diff --git a/src/Security/AuthorizationEngine.cs b/src/Security/AuthorizationEngine.cs new file mode 100644 index 000000000000..31ea94611d42 --- /dev/null +++ b/src/Security/AuthorizationEngine.cs @@ -0,0 +1,31 @@ +#if __MACOS__ +#nullable enable + +using System; +using System.Runtime.Versioning; +using CoreFoundation; +using ObjCRuntime; + +namespace Security { + + /// Represents an opaque reference to an authorization engine used in authorization plugin views. + public class AuthorizationEngine : NativeObject { + [Preserve (Conditional = true)] + internal AuthorizationEngine (NativeHandle handle, bool owns) + : base (handle, owns) + { + } +#if !COREBUILD + /// Creates an from a raw handle without taking ownership. + /// The native AuthorizationEngineRef handle. + /// A managed wrapper, or if the handle is zero. + public static AuthorizationEngine? Create (NativeHandle handle) + { + if (handle == IntPtr.Zero) + return null; + return new AuthorizationEngine (handle, owns: false); + } +#endif // !COREBUILD + } +} +#endif // __MACOS__ diff --git a/src/Security/SecKeychain.cs b/src/Security/SecKeychain.cs new file mode 100644 index 000000000000..31f097a6e66e --- /dev/null +++ b/src/Security/SecKeychain.cs @@ -0,0 +1,100 @@ +#if __MACOS__ +#nullable enable + +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using ObjCRuntime; +using CoreFoundation; + +namespace Security { + + /// Represents a keychain on macOS. + public class SecKeychain : NativeObject { + [Preserve (Conditional = true)] + internal SecKeychain (NativeHandle handle, bool owns) + : base (handle, owns) + { + } +#if !COREBUILD + [SupportedOSPlatform ("macos")] + [ObsoletedOSPlatform ("macos10.10")] + [DllImport (Constants.SecurityLibrary)] + extern static unsafe int /* OSStatus */ SecKeychainGetTypeID (); + + /// Returns the Core Foundation type identifier for SecKeychain. + /// The Core Foundation type identifier. + public static nint GetTypeID () + { + return SecKeychainGetTypeID (); + } + + [SupportedOSPlatform ("macos")] + [ObsoletedOSPlatform ("macos10.10")] + [DllImport (Constants.SecurityLibrary)] + extern static unsafe int /* OSStatus */ SecKeychainCopyDefault (IntPtr* keychain); + + /// Gets the default keychain. + /// The default , or on failure. + public static SecKeychain? GetDefault () + { + IntPtr handle; + int status; + unsafe { + status = SecKeychainCopyDefault (&handle); + } + if (status != 0 || handle == IntPtr.Zero) + return null; + return new SecKeychain (handle, owns: true); + } + + [SupportedOSPlatform ("macos")] + [ObsoletedOSPlatform ("macos10.10")] + [DllImport (Constants.SecurityLibrary)] + extern static unsafe int /* OSStatus */ SecKeychainOpen (IntPtr pathName, IntPtr* keychain); + + /// Opens the keychain at the specified file path. + /// The file system path of the keychain to open. + /// A for the specified path, or on failure. + public static SecKeychain? Open (string path) + { + if (path is null) + ObjCRuntime.ThrowHelper.ThrowArgumentNullException (nameof (path)); + using var pathStr = new TransientString (path); + IntPtr handle; + int status; + unsafe { + status = SecKeychainOpen (pathStr, &handle); + } + if (status != 0 || handle == IntPtr.Zero) + return null; + return new SecKeychain (handle, owns: true); + } + + [SupportedOSPlatform ("macos")] + [ObsoletedOSPlatform ("macos10.10")] + [DllImport (Constants.SecurityLibrary)] + extern static unsafe int /* OSStatus */ SecKeychainGetPath (IntPtr keychainRef, int* ioPathLength, IntPtr pathName); + + /// Gets the file system path of this keychain. + /// The POSIX path of the keychain, or on failure. + public string? GetPath () + { + int pathLength = 1024; + IntPtr buffer = Marshal.AllocHGlobal (pathLength); + try { + int status; + unsafe { + status = SecKeychainGetPath (Handle, &pathLength, buffer); + } + if (status != 0) + return null; + return Marshal.PtrToStringUTF8 (buffer, pathLength); + } finally { + Marshal.FreeHGlobal (buffer); + } + } +#endif // !COREBUILD + } +} +#endif // __MACOS__ diff --git a/src/Security/SecKeychainSettings.cs b/src/Security/SecKeychainSettings.cs new file mode 100644 index 000000000000..de9b84e28d88 --- /dev/null +++ b/src/Security/SecKeychainSettings.cs @@ -0,0 +1,50 @@ +#if __MACOS__ +#nullable enable + +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Security { + + /// Represents keychain settings for lock behavior on macOS. + [StructLayout (LayoutKind.Sequential)] + public struct SecKeychainSettings { + int /* UInt32 */ version; + byte /* Boolean */ lockOnSleep; + byte /* Boolean */ useLockInterval; + int /* UInt32 */ lockInterval; + + /// Gets or sets the version of this settings structure. + public int Version { + get => version; + set => version = value; + } + + /// Gets or sets whether the keychain locks when the system sleeps. + public bool LockOnSleep { + get => lockOnSleep != 0; + set => lockOnSleep = value ? (byte) 1 : (byte) 0; + } + + /// Gets or sets whether the lock interval is used. + public bool UseLockInterval { + get => useLockInterval != 0; + set => useLockInterval = value ? (byte) 1 : (byte) 0; + } + + /// Gets or sets the number of seconds before the keychain auto-locks. + public int LockInterval { + get => lockInterval; + set => lockInterval = value; + } + + /// Creates a new with the current version. + /// A new initialized with version 1. + public static SecKeychainSettings Create () + { + return new SecKeychainSettings { version = 1 }; + } + } +} +#endif // __MACOS__ diff --git a/src/SecurityInterface/AuthorizationCallbacks.cs b/src/SecurityInterface/AuthorizationCallbacks.cs new file mode 100644 index 000000000000..2a70ffc76945 --- /dev/null +++ b/src/SecurityInterface/AuthorizationCallbacks.cs @@ -0,0 +1,262 @@ +#nullable enable + +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; +using Foundation; +using ObjCRuntime; +using Security; + +namespace SecurityInterface { + + /// Specifies the result of an authorization operation. + [SupportedOSPlatform ("macos")] + public enum AuthorizationResult : uint { + /// The authorization was granted. + Allow = 0, + /// The authorization was denied. + Deny = 1, + /// The result is undefined. + Undefined = 2, + /// The user cancelled the authorization. + UserCanceled = 3, + } + + /// Flags that describe properties of an authorization context value. + [SupportedOSPlatform ("macos")] + [Flags] + public enum AuthorizationContextFlags : uint { + /// The value can be extracted by the client. + Extractable = 1 << 0, + /// The value is volatile and should not be persisted. + Volatile = 1 << 1, + /// The value is sticky and persists across mechanism evaluations. + Sticky = 1 << 2, + } + + [StructLayout (LayoutKind.Sequential)] + unsafe struct AuthorizationValueNative { + public nuint Length; + public void* Data; + } + + [StructLayout (LayoutKind.Sequential)] + unsafe struct AuthorizationValueVectorNative { + public uint Count; + public AuthorizationValueNative* Values; + } + + [StructLayout (LayoutKind.Sequential)] + unsafe struct AuthorizationCallbacksNative { + public uint Version; + public delegate* unmanaged SetResult; + public delegate* unmanaged RequestInterrupt; + public delegate* unmanaged DidDeactivate; + public delegate* unmanaged GetContextValue; + public delegate* unmanaged SetContextValue; + public delegate* unmanaged GetHintValue; + public delegate* unmanaged SetHintValue; + public delegate* unmanaged GetArguments; + public delegate* unmanaged GetSessionId; + public delegate* unmanaged GetImmutableHintValue; + public delegate* unmanaged GetLAContext; + public delegate* unmanaged GetTokenIdentities; + public delegate* unmanaged GetTKTokenWatcher; + public delegate* unmanaged RemoveHintValue; + public delegate* unmanaged RemoveContextValue; + } + + /// Wraps the native AuthorizationCallbacks structure provided by the authorization engine host for communicating with the Security Server. + [SupportedOSPlatform ("macos")] + public unsafe sealed class AuthorizationCallbacks : INativeObject { + readonly NativeHandle handle; + + /// Creates an from a native callbacks pointer. + /// The pointer to the native AuthorizationCallbacks structure. + public AuthorizationCallbacks (NativeHandle handle) + { + this.handle = handle; + } + + /// Gets the native handle for this callbacks structure. + public NativeHandle Handle => handle; + + internal static AuthorizationCallbacks? Create (NativeHandle handle) + { + if (handle == NativeHandle.Zero) + return null; + return new AuthorizationCallbacks (handle); + } + + AuthorizationCallbacksNative* Native { + get { + if (handle == NativeHandle.Zero) + throw new ObjectDisposedException (nameof (AuthorizationCallbacks)); + return (AuthorizationCallbacksNative*) handle; + } + } + + /// Gets the version of the callbacks structure. + public uint Version => Native->Version; + + static byte [] ToNullTerminatedUtf8 (string value) + { + var utf8 = Encoding.UTF8.GetBytes (value); + var rv = new byte [utf8.Length + 1]; + Buffer.BlockCopy (utf8, 0, rv, 0, utf8.Length); + return rv; + } + + static byte []? CopyValue (AuthorizationValueNative* value) + { + if (value is null || value->Length == 0) + return null; + var length = checked((int) value->Length); + var rv = new byte [length]; + Marshal.Copy ((IntPtr) value->Data, rv, 0, length); + return rv; + } + + /// Sets the result of the current authorization evaluation. + /// The authorization engine. + /// The authorization result. + /// An OSStatus code; 0 on success. + public int SetResult (AuthorizationEngine engine, AuthorizationResult result) + { + var rv = Native->SetResult (engine.GetNonNullHandle (nameof (engine)), result); + GC.KeepAlive (engine); + return rv; + } + + /// Requests an interrupt of the current authorization evaluation. + /// The authorization engine. + /// An OSStatus code; 0 on success. + public int RequestInterrupt (AuthorizationEngine engine) + { + var rv = Native->RequestInterrupt (engine.GetNonNullHandle (nameof (engine))); + GC.KeepAlive (engine); + return rv; + } + + /// Notifies the engine that the mechanism has deactivated. + /// The authorization engine. + /// An OSStatus code; 0 on success. + public int DidDeactivate (AuthorizationEngine engine) + { + var rv = Native->DidDeactivate (engine.GetNonNullHandle (nameof (engine))); + GC.KeepAlive (engine); + return rv; + } + + /// Gets a context value for the specified key. + /// The authorization engine. + /// The context key name. + /// On return, the flags associated with the context value. + /// On return, the context value as a byte array, or if not found. + /// An OSStatus code; 0 on success. + public int GetContextValue (AuthorizationEngine engine, string key, out AuthorizationContextFlags contextFlags, out byte []? value) + { + if (key is null) + ThrowHelper.ThrowArgumentNullException (nameof (key)); + fixed (byte* keyPtr = ToNullTerminatedUtf8 (key)) { + AuthorizationContextFlags flags = default; + AuthorizationValueNative* nativeValue = null; + var rv = Native->GetContextValue (engine.GetNonNullHandle (nameof (engine)), (IntPtr) keyPtr, &flags, &nativeValue); + contextFlags = flags; + value = rv == 0 ? CopyValue (nativeValue) : null; + GC.KeepAlive (engine); + return rv; + } + } + + /// Sets a context value for the specified key. + /// The authorization engine. + /// The context key name. + /// The flags to associate with the value. + /// The value to set. + /// An OSStatus code; 0 on success. + public int SetContextValue (AuthorizationEngine engine, string key, AuthorizationContextFlags contextFlags, byte [] value) + { + if (key is null) + ThrowHelper.ThrowArgumentNullException (nameof (key)); + if (value is null) + ThrowHelper.ThrowArgumentNullException (nameof (value)); + fixed (byte* keyPtr = ToNullTerminatedUtf8 (key)) + fixed (byte* valuePtr = value) { + var nativeValue = new AuthorizationValueNative { Length = (nuint) value.Length, Data = valuePtr }; + var rv = Native->SetContextValue (engine.GetNonNullHandle (nameof (engine)), (IntPtr) keyPtr, contextFlags, &nativeValue); + GC.KeepAlive (engine); + return rv; + } + } + + /// Gets a hint value for the specified key. + /// The authorization engine. + /// The hint key name. + /// On return, the hint value as a byte array, or if not found. + /// An OSStatus code; 0 on success. + public int GetHintValue (AuthorizationEngine engine, string key, out byte []? value) + { + if (key is null) + ThrowHelper.ThrowArgumentNullException (nameof (key)); + fixed (byte* keyPtr = ToNullTerminatedUtf8 (key)) { + AuthorizationValueNative* nativeValue = null; + var rv = Native->GetHintValue (engine.GetNonNullHandle (nameof (engine)), (IntPtr) keyPtr, &nativeValue); + value = rv == 0 ? CopyValue (nativeValue) : null; + GC.KeepAlive (engine); + return rv; + } + } + + /// Sets a hint value for the specified key. + /// The authorization engine. + /// The hint key name. + /// The value to set. + /// An OSStatus code; 0 on success. + public int SetHintValue (AuthorizationEngine engine, string key, byte [] value) + { + if (key is null) + ThrowHelper.ThrowArgumentNullException (nameof (key)); + if (value is null) + ThrowHelper.ThrowArgumentNullException (nameof (value)); + fixed (byte* keyPtr = ToNullTerminatedUtf8 (key)) + fixed (byte* valuePtr = value) { + var nativeValue = new AuthorizationValueNative { Length = (nuint) value.Length, Data = valuePtr }; + var rv = Native->SetHintValue (engine.GetNonNullHandle (nameof (engine)), (IntPtr) keyPtr, &nativeValue); + GC.KeepAlive (engine); + return rv; + } + } + + /// Removes a hint value for the specified key. + /// The authorization engine. + /// The hint key name to remove. + /// An OSStatus code; 0 on success. + public int RemoveHintValue (AuthorizationEngine engine, string key) + { + if (key is null) + ThrowHelper.ThrowArgumentNullException (nameof (key)); + fixed (byte* keyPtr = ToNullTerminatedUtf8 (key)) { + var rv = Native->RemoveHintValue (engine.GetNonNullHandle (nameof (engine)), (IntPtr) keyPtr); + GC.KeepAlive (engine); + return rv; + } + } + + /// Removes a context value for the specified key. + /// The authorization engine. + /// The context key name to remove. + /// An OSStatus code; 0 on success. + public int RemoveContextValue (AuthorizationEngine engine, string key) + { + if (key is null) + ThrowHelper.ThrowArgumentNullException (nameof (key)); + fixed (byte* keyPtr = ToNullTerminatedUtf8 (key)) { + var rv = Native->RemoveContextValue (engine.GetNonNullHandle (nameof (engine)), (IntPtr) keyPtr); + GC.KeepAlive (engine); + return rv; + } + } + } +} diff --git a/src/SecurityInterface/AuthorizationRights.cs b/src/SecurityInterface/AuthorizationRights.cs new file mode 100644 index 000000000000..b733e676e744 --- /dev/null +++ b/src/SecurityInterface/AuthorizationRights.cs @@ -0,0 +1,217 @@ +#nullable enable + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Text; +using ObjCRuntime; + +namespace SecurityInterface { + + /// Represents a single authorization right with a name, optional value, and flags. + [SupportedOSPlatform ("macos")] + public readonly struct AuthorizationRight { + readonly byte []? value; + + /// Gets the name of the authorization right. + public string Name { get; } + + /// Gets the flags associated with this right. + public uint Flags { get; } + + /// Gets a copy of the value data, or if no value is set. + public byte []? Value => value is null ? null : (byte []) value.Clone (); + + /// Creates a new authorization right with the specified name, optional value, and flags. + /// The authorization right name. + /// The optional value data. + /// The flags for this right. + public AuthorizationRight (string name, byte []? value = null, uint flags = 0) + { + if (name is null) + ThrowHelper.ThrowArgumentNullException (nameof (name)); + Name = name; + this.value = value is null ? null : (byte []) value.Clone (); + Flags = flags; + } + + internal byte []? GetRawValue () => value; + } + + [StructLayout (LayoutKind.Sequential)] + unsafe struct AuthorizationItemNative { + public IntPtr Name; + public nuint ValueLength; + public IntPtr Value; + public uint Flags; + } + + [StructLayout (LayoutKind.Sequential)] + unsafe struct AuthorizationRightsNative { + public uint Count; + public AuthorizationItemNative* Items; + } + + /// Represents a set of authorization rights used to configure an . + [SupportedOSPlatform ("macos")] + public unsafe sealed class AuthorizationRights : IDisposable, INativeObject, IReadOnlyList { + NativeHandle handle; + readonly AuthorizationRight [] items; + + /// Creates a new authorization rights set from the specified rights. + /// The authorization rights to include. + public AuthorizationRights (params AuthorizationRight [] items) + : this ((IEnumerable) items) + { + } + + /// Creates a new authorization rights set from the specified right names. + /// The authorization right names. + public AuthorizationRights (params string [] rights) + { + if (rights is null) + ThrowHelper.ThrowArgumentNullException (nameof (rights)); + items = new AuthorizationRight [rights.Length]; + for (int i = 0; i < rights.Length; i++) + items [i] = new AuthorizationRight (rights [i]); + AllocateNative (); + } + + /// Creates a new authorization rights set from the specified rights. + /// The authorization rights to include. + public AuthorizationRights (IEnumerable items) + { + if (items is null) + ThrowHelper.ThrowArgumentNullException (nameof (items)); + var list = new List (); + foreach (var item in items) + list.Add (new AuthorizationRight (item.Name, item.GetRawValue (), item.Flags)); + this.items = list.ToArray (); + AllocateNative (); + } + + AuthorizationRights (AuthorizationRight [] items, bool noCopy) + { + this.items = items; + AllocateNative (); + } + + ~AuthorizationRights () + { + Dispose (false); + } + + /// Gets the native handle to the AuthorizationRights structure. + public NativeHandle Handle => handle; + + /// Gets the number of rights in this set. + public int Count => items.Length; + + /// Gets the authorization right at the specified index. + /// The zero-based index. + public AuthorizationRight this [int index] => items [index]; + + /// Returns an enumerator that iterates through the authorization rights. + public IEnumerator GetEnumerator () => ((IEnumerable) items).GetEnumerator (); + + IEnumerator IEnumerable.GetEnumerator () => items.GetEnumerator (); + + /// Creates an by reading from a native AuthorizationRights pointer. + /// The pointer to the native AuthorizationRights structure, or zero for . + /// A new managed rights set cloned from the native data, or if the handle is zero. + public static AuthorizationRights? FromHandle (NativeHandle handle) + { + if (handle == NativeHandle.Zero) + return null; + + var native = (AuthorizationRightsNative*) handle; + var managedItems = new AuthorizationRight [native->Count]; + + for (int i = 0; i < native->Count; i++) { + var item = native->Items [i]; + var name = Marshal.PtrToStringUTF8 (item.Name)!; + byte []? value = null; + + if (item.ValueLength != 0) { + var length = checked((int) item.ValueLength); + value = new byte [length]; + Marshal.Copy (item.Value, value, 0, length); + } + + managedItems [i] = new AuthorizationRight (name, value, item.Flags); + } + + return new AuthorizationRights (managedItems, noCopy: true); + } + + void AllocateNative () + { + handle = Marshal.AllocHGlobal (sizeof (AuthorizationRightsNative)); + var native = (AuthorizationRightsNative*) handle; + native->Count = (uint) items.Length; + + if (items.Length == 0) { + native->Items = null; + return; + } + + native->Items = (AuthorizationItemNative*) Marshal.AllocHGlobal (sizeof (AuthorizationItemNative) * items.Length); + + for (int i = 0; i < items.Length; i++) { + var item = items [i]; + var value = item.GetRawValue (); + native->Items [i] = new AuthorizationItemNative { + Name = StringToUtf8 (item.Name), + ValueLength = value is null ? 0 : (nuint) value.Length, + Value = value is null || value.Length == 0 ? IntPtr.Zero : BytesToHGlobal (value), + Flags = item.Flags, + }; + } + } + + static IntPtr StringToUtf8 (string value) + { + var bytes = Encoding.UTF8.GetBytes (value); + var ptr = Marshal.AllocHGlobal (bytes.Length + 1); + Marshal.Copy (bytes, 0, ptr, bytes.Length); + Marshal.WriteByte (ptr, bytes.Length, 0); + return ptr; + } + + static IntPtr BytesToHGlobal (byte [] value) + { + var ptr = Marshal.AllocHGlobal (value.Length); + Marshal.Copy (value, 0, ptr, value.Length); + return ptr; + } + + /// Releases all unmanaged memory associated with this rights set. + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + void Dispose (bool disposing) + { + if (handle == NativeHandle.Zero) + return; + + var native = (AuthorizationRightsNative*) handle; + if (native->Items is not null) { + for (int i = 0; i < native->Count; i++) { + if (native->Items [i].Name != IntPtr.Zero) + Marshal.FreeHGlobal (native->Items [i].Name); + if (native->Items [i].Value != IntPtr.Zero) + Marshal.FreeHGlobal (native->Items [i].Value); + } + Marshal.FreeHGlobal ((IntPtr) native->Items); + } + + Marshal.FreeHGlobal (handle); + handle = NativeHandle.Zero; + } + } +} diff --git a/src/SecurityInterface/Enums.cs b/src/SecurityInterface/Enums.cs new file mode 100644 index 000000000000..5299c733278c --- /dev/null +++ b/src/SecurityInterface/Enums.cs @@ -0,0 +1,43 @@ +#nullable enable + +using System; +using System.Runtime.Versioning; +using ObjCRuntime; + +namespace SecurityInterface { + + /// Defines the authorization states of an . + [SupportedOSPlatform ("macos")] + public enum SFAuthorizationViewState : int { + /// The initial state before the first status update. + Startup = 0, + /// The view is locked, indicating the user is not authorized. + Locked, + /// An authorization operation is in progress. + InProgress, + /// The view is unlocked, indicating the user is authorized. + Unlocked, + } + + /// Identifies button types in an authorization plugin view. + [SupportedOSPlatform ("macos")] + public enum SFButtonType : int { + /// The cancel button. + Cancel = 0, + /// The OK button. + Ok = 1, + /// The back button. This has the same value as . + Back = 0, + /// The login button. This has the same value as . + Login = 1, + } + + /// Specifies the type of view requested from an authorization plugin view. + [SupportedOSPlatform ("macos")] + public enum SFViewType : int { + /// A view showing both identity and credentials fields. + IdentityAndCredentials = 0, + /// A view showing only credentials fields. + Credentials, + } +} diff --git a/src/SecurityInterface/SFAuthorizationPluginView.cs b/src/SecurityInterface/SFAuthorizationPluginView.cs new file mode 100644 index 000000000000..6ed2efc72b46 --- /dev/null +++ b/src/SecurityInterface/SFAuthorizationPluginView.cs @@ -0,0 +1,30 @@ +#nullable enable + +using System; +using Foundation; +using ObjCRuntime; +using Security; + +namespace SecurityInterface { + + public partial class SFAuthorizationPluginView { + + /// Initializes the view with the authorization callbacks and engine reference provided by the plugin host. + /// The authorization callbacks for communicating with the Security Server. + /// The authorization engine reference. + public SFAuthorizationPluginView (AuthorizationCallbacks callbacks, AuthorizationEngine engineRef) + : base (NSObjectFlag.Empty) + { + if (callbacks is null) + ThrowHelper.ThrowArgumentNullException (nameof (callbacks)); + if (engineRef is null) + ThrowHelper.ThrowArgumentNullException (nameof (engineRef)); + InitializeHandle (_InitWithCallbacks (callbacks.Handle, engineRef), "initWithCallbacks:andEngineRef:"); + GC.KeepAlive (callbacks); + GC.KeepAlive (engineRef); + } + + /// Gets the authorization callbacks structure for communicating with the Security Server. + public AuthorizationCallbacks? Callbacks => AuthorizationCallbacks.Create (_Callbacks); + } +} diff --git a/src/SecurityInterface/SFAuthorizationView.cs b/src/SecurityInterface/SFAuthorizationView.cs new file mode 100644 index 000000000000..e0f2a2d9d188 --- /dev/null +++ b/src/SecurityInterface/SFAuthorizationView.cs @@ -0,0 +1,30 @@ +#nullable enable + +using System; +using ObjCRuntime; + +namespace SecurityInterface { + + /// A view that displays a lock icon for controlling access to a privileged operation. + public partial class SFAuthorizationView { + + /// Sets the authorization right string to check for. + /// The authorization right name as a UTF-8 string. + public void SetAuthorizationString (string authorizationString) + { + if (authorizationString is null) + ThrowHelper.ThrowArgumentNullException (nameof (authorizationString)); + using var str = new TransientString (authorizationString); + _SetAuthorizationString (str); + } + + /// Gets or sets the authorization rights to check for. + public AuthorizationRights? AuthorizationRightsSet { + get => AuthorizationRights.FromHandle (_AuthorizationRights); + set { + _SetAuthorizationRights (value?.Handle ?? NativeHandle.Zero); + GC.KeepAlive (value); + } + } + } +} diff --git a/src/bgen/Caches/TypeCache.cs b/src/bgen/Caches/TypeCache.cs index c6dba9daf63f..289603c814bf 100644 --- a/src/bgen/Caches/TypeCache.cs +++ b/src/bgen/Caches/TypeCache.cs @@ -74,8 +74,11 @@ public class TypeCache { public Type SCNVector3 { get; } public Type SCNVector4 { get; } public Type SecAccessControl { get; } + public Type? AuthorizationEngine { get; } + public Type SecCertificate { get; } public Type SecIdentity { get; } public Type SecKey { get; } + public Type? SecKeychain { get; } public Type SecTrust { get; } public Type SecProtocolMetadata { get; } public Type SecProtocolOptions { get; } @@ -235,6 +238,11 @@ public TypeCache (MetadataLoadContext universe, Frameworks frameworks, PlatformN SCNVector4 = Lookup (platformAssembly, "SceneKit", "SCNVector4"); SCNMatrix4 = Lookup (platformAssembly, "SceneKit", "SCNMatrix4"); SecAccessControl = Lookup (platformAssembly, "Security", "SecAccessControl"); + if (frameworks.HaveSecurityInterface) { + AuthorizationEngine = ConditionalLookup (platformAssembly, "Security", "AuthorizationEngine"); + SecKeychain = ConditionalLookup (platformAssembly, "Security", "SecKeychain"); + } + SecCertificate = Lookup (platformAssembly, "Security", "SecCertificate"); SecIdentity = Lookup (platformAssembly, "Security", "SecIdentity"); SecKey = Lookup (platformAssembly, "Security", "SecKey"); SecTrust = Lookup (platformAssembly, "Security", "SecTrust"); diff --git a/src/bgen/Models/MarshalTypeList.cs b/src/bgen/Models/MarshalTypeList.cs index c1ef12e548f2..587f99f978a7 100644 --- a/src/bgen/Models/MarshalTypeList.cs +++ b/src/bgen/Models/MarshalTypeList.cs @@ -71,6 +71,11 @@ public void Load (TypeCache typeCache, Frameworks frameworks) Add (typeCache.AudioUnit); Add (typeCache.SecIdentity); Add (typeCache.SecIdentity2); + if (typeCache.AuthorizationEngine is not null) + Add (typeCache.AuthorizationEngine); + Add (typeCache.SecCertificate); + if (typeCache.SecKeychain is not null) + Add (typeCache.SecKeychain); Add (typeCache.SecKey); Add (typeCache.SecTrust); Add (typeCache.SecTrust2); diff --git a/src/build/dotnet/generator-frameworks.g.cs b/src/build/dotnet/generator-frameworks.g.cs index 9346b0afb5e1..67f777b38d76 100644 --- a/src/build/dotnet/generator-frameworks.g.cs +++ b/src/build/dotnet/generator-frameworks.g.cs @@ -274,6 +274,7 @@ partial class Frameworks { "ScriptingBridge", "SearchKit", "Security", + "SecurityInterface", "SecurityUI", "SensitiveContentAnalysis", "ServiceManagement", @@ -672,6 +673,7 @@ partial class Frameworks { bool? _ScriptingBridge; bool? _SearchKit; bool? _Security; + bool? _SecurityInterface; bool? _SecurityUI; bool? _SensitiveContentAnalysis; bool? _SensorKit; @@ -846,6 +848,7 @@ partial class Frameworks { public bool HaveScriptingBridge { get { if (!_ScriptingBridge.HasValue) _ScriptingBridge = GetValue ("ScriptingBridge"); return _ScriptingBridge.Value; } } public bool HaveSearchKit { get { if (!_SearchKit.HasValue) _SearchKit = GetValue ("SearchKit"); return _SearchKit.Value; } } public bool HaveSecurity { get { if (!_Security.HasValue) _Security = GetValue ("Security"); return _Security.Value; } } + public bool HaveSecurityInterface { get { if (!_SecurityInterface.HasValue) _SecurityInterface = GetValue ("SecurityInterface"); return _SecurityInterface.Value; } } public bool HaveSecurityUI { get { if (!_SecurityUI.HasValue) _SecurityUI = GetValue ("SecurityUI"); return _SecurityUI.Value; } } public bool HaveSensitiveContentAnalysis { get { if (!_SensitiveContentAnalysis.HasValue) _SensitiveContentAnalysis = GetValue ("SensitiveContentAnalysis"); return _SensitiveContentAnalysis.Value; } } public bool HaveSensorKit { get { if (!_SensorKit.HasValue) _SensorKit = GetValue ("SensorKit"); return _SensorKit.Value; } } diff --git a/src/frameworks.sources b/src/frameworks.sources index a8254313ba3f..9222cbe031bf 100644 --- a/src/frameworks.sources +++ b/src/frameworks.sources @@ -1571,8 +1571,11 @@ SECURITY_API_SOURCES = \ Security/SecureTransport.cs \ SECURITY_CORE_SOURCES = \ + Security/AuthorizationEngine.cs \ Security/Certificate.cs \ Security/SecAccessControl.cs \ + Security/SecKeychain.cs \ + Security/SecKeychainSettings.cs \ Security/SecProtocolMetadata.cs \ Security/SecProtocolOptions.cs \ Security/Trust.cs \ @@ -1593,6 +1596,17 @@ SECURITY_SOURCES = \ Security/SslConnection.cs \ Security/SslContext.cs \ +# SecurityInterface + +SECURITYINTERFACE_CORE_SOURCES = \ + SecurityInterface/Enums.cs \ + +SECURITYINTERFACE_SOURCES = \ + SecurityInterface/AuthorizationCallbacks.cs \ + SecurityInterface/AuthorizationRights.cs \ + SecurityInterface/SFAuthorizationPluginView.cs \ + SecurityInterface/SFAuthorizationView.cs \ + # SensitiveContentAnalysis SENSITIVECONTENTANALYSIS_SOURCES = \ @@ -2094,6 +2108,7 @@ MACOS_FRAMEWORKS = \ SensitiveContentAnalysis \ ServiceManagement \ Security \ + SecurityInterface \ SecurityUI \ SharedWithYou \ SharedWithYouCore \ diff --git a/src/rsp/dotnet/macos-defines-dotnet.rsp b/src/rsp/dotnet/macos-defines-dotnet.rsp index 17b1bd296208..2a7d3918a7f6 100644 --- a/src/rsp/dotnet/macos-defines-dotnet.rsp +++ b/src/rsp/dotnet/macos-defines-dotnet.rsp @@ -117,6 +117,7 @@ -d:HAS_SCRIPTINGBRIDGE -d:HAS_SEARCHKIT -d:HAS_SECURITY +-d:HAS_SECURITYINTERFACE -d:HAS_SECURITYUI -d:HAS_SENSITIVECONTENTANALYSIS -d:HAS_SERVICEMANAGEMENT diff --git a/src/securityinterface.cs b/src/securityinterface.cs new file mode 100644 index 000000000000..268dcbf8972f --- /dev/null +++ b/src/securityinterface.cs @@ -0,0 +1,659 @@ +// +// securityinterface.cs: Bindings for the SecurityInterface framework (macOS only) +// +// Copyright 2025 Microsoft Corp. +// + +#nullable enable + +using System; +using AppKit; +using Foundation; +using ObjCRuntime; +using Security; + +namespace SecurityInterface { + + /// Host view for authorization plugin mechanisms, subclassed to provide custom UI for the loginwindow authorization process. + [BaseType (typeof (NSObject))] + [DisableDefaultCtor] + interface SFAuthorizationPluginView { + + [Internal] + [Export ("initWithCallbacks:andEngineRef:")] + NativeHandle _InitWithCallbacks (IntPtr callbacks, AuthorizationEngine engineRef); + + /// Gets the authorization engine reference for communicating with the Security Server. + [Export ("engineRef")] + AuthorizationEngine EngineRef { get; } + + [Internal] + [Export ("callbacks")] + IntPtr _Callbacks { get; } + + /// Called when the user presses a button in the UI. + /// The type of button that was pressed. + [Export ("buttonPressed:")] + void ButtonPressed (SFButtonType buttonType); + + /// Returns the last error that occurred during the authorization process. + /// An describing the last error, or if no error occurred. + [Export ("lastError")] + [return: NullAllowed] + NSError GetLastError (); + + /// Called after the view has been activated. + [Export ("didActivate")] + void DidActivate (); + + /// Called before the view activates, providing a dictionary of user information. + /// A dictionary containing user information, or . + [Export ("willActivateWithUser:")] + void WillActivateWithUser ([NullAllowed] NSDictionary userInformation); + + /// Called after the view has been deactivated. + [Export ("didDeactivate")] + void DidDeactivate (); + + /// Returns the first view in the keyboard focus chain. + /// The first in the key view loop, or . + [Export ("firstKeyView")] + [return: NullAllowed] + NSView GetFirstKeyView (); + + /// Returns the first responder for the view. + /// The first , or . + [Export ("firstResponder")] + [return: NullAllowed] + NSResponder GetFirstResponder (); + + /// Returns the last view in the keyboard focus chain. + /// The last in the key view loop, or . + [Export ("lastKeyView")] + [return: NullAllowed] + NSView GetLastKeyView (); + + /// Enables or disables the view. + /// to enable the view; to disable it. + [Export ("setEnabled:")] + void SetEnabled (bool enabled); + + /// Returns the view for the specified view type. + /// The type of view to retrieve. + /// The for the specified type, or . + [Export ("viewForType:")] + [return: NullAllowed] + NSView GetView (SFViewType viewType); + + /// Displays the authorization plugin view. + [Export ("displayView")] + void DisplayView (); + + /// Enables or disables the specified button. + /// The button to enable or disable. + /// to enable the button; to disable it. + [Export ("setButton:enabled:")] + void SetButton (SFButtonType buttonType, bool enabled); + + /// Updates the view to reflect current state. + [Export ("updateView")] + void UpdateView (); + } + + /// Interface representing the protocol methods of . + interface ISFAuthorizationViewDelegate { } + + /// Delegate methods for responding to authorization state changes in an . + [Protocol (IsInformal = true, BackwardsCompatibleCodeGeneration = false), Model] + [BaseType (typeof (NSObject))] + interface SFAuthorizationViewDelegate { + + /// Called when the authorization view has been authorized. + /// The that was authorized. + [Export ("authorizationViewDidAuthorize:")] + void DidAuthorize (SFAuthorizationView view); + + /// Called when the authorization view has been deauthorized. + /// The that was deauthorized. + [Export ("authorizationViewDidDeauthorize:")] + void DidDeauthorize (SFAuthorizationView view); + + /// Called to determine whether the authorization view should deauthorize. + /// The requesting deauthorization. + /// to allow deauthorization; otherwise, . + [Export ("authorizationViewShouldDeauthorize:")] + bool ShouldDeauthorize (SFAuthorizationView view); + + /// Called when the authorization view has created an authorization reference. + /// The that created the authorization. + [Export ("authorizationViewCreatedAuthorization:")] + void CreatedAuthorization (SFAuthorizationView view); + + /// Called when the authorization view has released its authorization reference. + /// The that released the authorization. + [Export ("authorizationViewReleasedAuthorization:")] + void ReleasedAuthorization (SFAuthorizationView view); + + /// Called when the authorization view has been hidden. + /// The that was hidden. + [Export ("authorizationViewDidHide:")] + void DidHide (SFAuthorizationView view); + } + + /// A view that displays a lock icon for controlling access to a privileged operation. + [BaseType (typeof (NSView))] + interface SFAuthorizationView { + + /// Initializes the view with the specified frame rectangle. + /// The frame rectangle for the view. + [Export ("initWithFrame:")] + NativeHandle Constructor (CoreGraphics.CGRect frameRect); + + [Internal] + [Export ("setString:")] + void _SetAuthorizationString (IntPtr authorizationString); + + [Internal] + [Export ("setAuthorizationRights:")] + void _SetAuthorizationRights (IntPtr authorizationRights); + + [Internal] + [Export ("authorizationRights")] + IntPtr _AuthorizationRights { get; } + + /// Gets the authorization object associated with this view, or if not yet authorized. + [Export ("authorization")] + [NullAllowed] + NSObject Authorization { get; } + + /// Updates the authorization status and lock icon state. + /// The object that initiated the update, or . + /// if the status was updated successfully; otherwise, . + [Export ("updateStatus:")] + bool UpdateStatus ([NullAllowed] NSObject sender); + + /// Enables or disables automatic status updates. + /// to enable auto-updating; to disable it. + [Export ("setAutoupdate:")] + void SetAutoupdate (bool autoupdate); + + /// Enables or disables automatic status updates with a specified interval. + /// to enable auto-updating; to disable it. + /// The interval in seconds between automatic updates. + [Export ("setAutoupdate:interval:")] + void SetAutoupdate (bool autoupdate, double interval); + + /// Gets the current authorization state of the view. + [Export ("authorizationState")] + SFAuthorizationViewState AuthorizationState { get; } + + /// Enables or disables the view. + /// to enable the view; to disable it. + [Export ("setEnabled:")] + void SetEnabled (bool enabled); + + /// Gets a value indicating whether the view is currently enabled. + [Export ("isEnabled")] + bool IsEnabled { get; } + + /// Sets the authorization flags as a bitmask of AuthorizationFlags values. + /// A bitmask of authorization flag values. + [Export ("setFlags:")] + void SetFlags (int flags); + + /// Gets or sets the weak delegate that receives authorization state change notifications. + [Export ("delegate", ArgumentSemantic.Weak)] + [NullAllowed] + NSObject WeakDelegate { get; set; } + + /// Gets or sets the delegate that receives authorization state change notifications. + [Wrap ("WeakDelegate")] + [NullAllowed] + ISFAuthorizationViewDelegate Delegate { get; set; } + + /// Attempts to authorize. + /// The object that initiated the authorization, or . + /// if authorization succeeded; otherwise, . + [Export ("authorize:")] + bool Authorize ([NullAllowed] NSObject sender); + + /// Attempts to deauthorize. + /// The object that initiated the deauthorization, or . + /// if deauthorization succeeded; otherwise, . + [Export ("deauthorize:")] + bool Deauthorize ([NullAllowed] NSObject sender); + } + + /// Interface representing the protocol methods of . + interface ISFCertificatePanelDelegate { } + + /// Delegate methods for the . + [Protocol (IsInformal = true, BackwardsCompatibleCodeGeneration = false), Model] + [BaseType (typeof (NSObject))] + interface SFCertificatePanelDelegate { + + /// Called when the user clicks the help button in the certificate panel. + /// The that sent the message. + /// if help was displayed; otherwise, . + [Export ("certificatePanelShowHelp:")] + bool ShowHelp (SFCertificatePanel sender); + } + + /// A panel that displays one or more certificates, presented as a modal dialog or a sheet. + [BaseType (typeof (NSPanel))] + interface SFCertificatePanel { + + /// Gets the shared certificate panel instance. + [Static] + [Export ("sharedCertificatePanel")] + SFCertificatePanel SharedCertificatePanel { get; } + + /// Displays the panel modally for the specified object. + /// The object containing the certificates to display. + /// Whether to display the certificate group. + /// The button code that was pressed to dismiss the panel. + [Export ("runModalForTrust:showGroup:")] + nint RunModalForTrust (SecTrust trust, bool showGroup); + + /// Displays the panel modally for the specified array of certificates. + /// An array of certificates to display. + /// Whether to display the certificate group. + /// The button code that was pressed to dismiss the panel. + [Export ("runModalForCertificates:showGroup:")] + nint RunModalForCertificates (NSArray certificates, bool showGroup); + + /// Displays the panel as a sheet for the specified object. + /// The window to which the sheet is attached. + /// The delegate that receives the did-end callback, or . + /// The selector invoked when the sheet ends, or . + /// A pointer to context information passed to the callback. + /// The object containing the certificates to display. + /// Whether to display the certificate group. + [Export ("beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:trust:showGroup:")] + void BeginSheet (NSWindow docWindow, [NullAllowed] NSObject modalDelegate, [NullAllowed] Selector didEndSelector, IntPtr contextInfo, SecTrust trust, bool showGroup); + + /// Displays the panel as a sheet for the specified array of certificates. + /// The window to which the sheet is attached. + /// The delegate that receives the did-end callback, or . + /// The selector invoked when the sheet ends, or . + /// A pointer to context information passed to the callback. + /// An array of certificates to display. + /// Whether to display the certificate group. + [Export ("beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:certificates:showGroup:")] + void BeginSheet (NSWindow docWindow, [NullAllowed] NSObject modalDelegate, [NullAllowed] Selector didEndSelector, IntPtr contextInfo, NSArray certificates, bool showGroup); + + /// Sets the policies used to evaluate the certificates. + /// An of SecPolicy objects, a single SecPolicy, or . + [Export ("setPolicies:")] + void SetPolicies ([NullAllowed] NSObject policies); + + /// Gets the policies used to evaluate the certificates. + [Export ("policies")] + NSArray Policies { get; } + + /// Sets the title of the default button. + /// The button title, or to use the default title. + [Export ("setDefaultButtonTitle:")] + void SetDefaultButtonTitle ([NullAllowed] string title); + + /// Sets the title of the alternate button. + /// The button title, or to hide the button. + [Export ("setAlternateButtonTitle:")] + void SetAlternateButtonTitle ([NullAllowed] string title); + + /// Sets whether the panel shows a help button. + /// to show the help button; otherwise, . + [Export ("setShowsHelp:")] + void SetShowsHelp (bool showsHelp); + + /// Gets a value indicating whether the panel shows a help button. + [Export ("showsHelp")] + bool ShowsHelp { get; } + + /// Sets the help anchor string for the help button. + /// The help anchor string, or . + [Export ("setHelpAnchor:")] + void SetHelpAnchor ([NullAllowed] string anchor); + + /// Gets the help anchor string. + [Export ("helpAnchor")] + [NullAllowed] + string HelpAnchor { get; } + + /// Gets the used to display certificate details. + [Export ("certificateView")] + SFCertificateView CertificateView { get; } + } + + /// A panel for making trust decisions about certificates that cannot be verified. + [BaseType (typeof (SFCertificatePanel))] + interface SFCertificateTrustPanel { + + /// Gets the shared certificate trust panel instance. + [Static] + [Export ("sharedCertificateTrustPanel")] + SFCertificateTrustPanel SharedCertificateTrustPanel { get; } + + /// Displays the panel modally for the specified object with a descriptive message. + /// The object to evaluate. + /// A message to display in the panel, or . + /// The button code that was pressed to dismiss the panel. + [Export ("runModalForTrust:message:")] + nint RunModalForTrust (SecTrust trust, [NullAllowed] string message); + + /// Displays the panel as a sheet for the specified object with a message. + /// The window to which the sheet is attached. + /// The delegate that receives the did-end callback, or . + /// The selector invoked when the sheet ends, or . + /// A pointer to context information passed to the callback. + /// The object to evaluate. + /// A message to display in the panel, or . + [Export ("beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:trust:message:")] + void BeginSheet (NSWindow docWindow, [NullAllowed] NSObject modalDelegate, [NullAllowed] Selector didEndSelector, IntPtr contextInfo, SecTrust trust, [NullAllowed] string message); + + /// Sets the informative text displayed in the panel. + /// The informative text string, or . + [Export ("setInformativeText:")] + void SetInformativeText ([NullAllowed] string informativeText); + + /// Gets the informative text displayed in the panel. + [Export ("informativeText")] + [NullAllowed] + string InformativeText { get; } + } + + /// A view that displays the contents of a certificate, with support for disclosable details and trust editing. + [BaseType (typeof (NSVisualEffectView))] + interface SFCertificateView { + + /// Initializes the view with the specified frame rectangle. + /// The frame rectangle for the view. + [Export ("initWithFrame:")] + NativeHandle Constructor (CoreGraphics.CGRect frameRect); + + /// Sets the certificate to display. + /// The to display, or to clear. + [Export ("setCertificate:")] + void SetCertificate ([NullAllowed] SecCertificate certificate); + + /// Gets the currently displayed certificate. + /// The being displayed, or . + [Export ("certificate")] + [return: NullAllowed] + SecCertificate GetCertificate (); + + /// Sets the policies used to evaluate the certificate trust. + /// An of SecPolicy objects, a single SecPolicy, or . + [Export ("setPolicies:")] + void SetPolicies ([NullAllowed] NSObject policies); + + /// Gets the policies used for trust evaluation. + [Export ("policies")] + NSArray Policies { get; } + + /// Sets whether the user can edit the trust settings. + /// to allow trust editing; otherwise, . + [Export ("setEditableTrust:")] + void SetEditableTrust (bool editable); + + /// Gets a value indicating whether the trust settings are editable. + [Export ("isEditable")] + bool IsEditable { get; } + + /// Sets whether trust information is displayed. + /// to show trust information; otherwise, . + [Export ("setDisplayTrust:")] + void SetDisplayTrust (bool display); + + /// Gets a value indicating whether trust information is currently displayed. + [Export ("isTrustDisplayed")] + bool IsTrustDisplayed { get; } + + /// Saves the current trust settings to the user's trust database. + [Export ("saveTrustSettings")] + void SaveTrustSettings (); + + /// Sets whether certificate details are displayed. + /// to show certificate details; otherwise, . + [Export ("setDisplayDetails:")] + void SetDisplayDetails (bool display); + + /// Gets a value indicating whether certificate details are displayed. + [Export ("detailsDisplayed")] + bool DetailsDisplayed { get; } + + /// Sets whether the details section is disclosed (expanded). + /// to expand the details section; otherwise, . + [Export ("setDetailsDisclosed:")] + void SetDetailsDisclosed (bool disclosed); + + /// Gets a value indicating whether the details section is disclosed. + [Export ("detailsDisclosed")] + bool DetailsDisclosed { get; } + + /// Sets whether the policies section is disclosed (expanded). + /// to expand the policies section; otherwise, . + [Export ("setPoliciesDisclosed:")] + void SetPoliciesDisclosed (bool disclosed); + + /// Gets a value indicating whether the policies section is disclosed. + [Export ("policiesDisclosed")] + bool PoliciesDisclosed { get; } + + /// Notification posted when the disclosure state of details or policies changes. + [Notification] + [Field ("SFCertificateViewDisclosureStateDidChange")] + NSString DisclosureStateDidChangeNotification { get; } + } + + /// Interface representing the protocol methods of . + interface ISFChooseIdentityPanelDelegate { } + + /// Delegate methods for the . + [Protocol (IsInformal = true, BackwardsCompatibleCodeGeneration = false), Model] + [BaseType (typeof (NSObject))] + interface SFChooseIdentityPanelDelegate { + + /// Called when the user clicks the help button in the choose identity panel. + /// The that sent the message. + /// if help was displayed; otherwise, . + [Export ("chooseIdentityPanelShowHelp:")] + bool ShowHelp (SFChooseIdentityPanel sender); + } + + /// A panel that lets the user choose a digital identity (certificate and private key pair) from a list. + [BaseType (typeof (NSPanel))] + interface SFChooseIdentityPanel { + + /// Gets the shared choose identity panel instance. + [Static] + [Export ("sharedChooseIdentityPanel")] + SFChooseIdentityPanel SharedChooseIdentityPanel { get; } + + /// Displays the panel modally with the specified array of identities and a message. + /// An array of objects to display. + /// A message to display in the panel, or . + /// The button code that was pressed to dismiss the panel. + [Export ("runModalForIdentities:message:")] + nint RunModalForIdentities (NSArray identities, [NullAllowed] string message); + + /// Displays the panel as a sheet with the specified identities and message. + /// The window to which the sheet is attached. + /// The delegate that receives the did-end callback, or . + /// The selector invoked when the sheet ends, or . + /// A pointer to context information passed to the callback. + /// An array of objects to display. + /// A message to display in the panel, or . + [Export ("beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:identities:message:")] + void BeginSheet (NSWindow docWindow, [NullAllowed] NSObject modalDelegate, [NullAllowed] Selector didEndSelector, IntPtr contextInfo, NSArray identities, [NullAllowed] string message); + + /// Gets the identity that the user chose from the list. + [Export ("identity")] + [NullAllowed] + SecIdentity Identity { get; } + + /// Sets the policies used to evaluate the identities. + /// An of SecPolicy objects, a single SecPolicy, or . + [Export ("setPolicies:")] + void SetPolicies ([NullAllowed] NSObject policies); + + /// Gets the policies used to evaluate the identities. + [Export ("policies")] + NSArray Policies { get; } + + /// Sets the title of the default button. + /// The button title, or to use the default title. + [Export ("setDefaultButtonTitle:")] + void SetDefaultButtonTitle ([NullAllowed] string title); + + /// Sets the title of the alternate button. + /// The button title, or to hide the button. + [Export ("setAlternateButtonTitle:")] + void SetAlternateButtonTitle ([NullAllowed] string title); + + /// Sets whether the panel shows a help button. + /// to show the help button; otherwise, . + [Export ("setShowsHelp:")] + void SetShowsHelp (bool showsHelp); + + /// Gets a value indicating whether the panel shows a help button. + [Export ("showsHelp")] + bool ShowsHelp { get; } + + /// Sets the help anchor string for the help button. + /// The help anchor string, or . + [Export ("setHelpAnchor:")] + void SetHelpAnchor ([NullAllowed] string anchor); + + /// Gets the help anchor string. + [Export ("helpAnchor")] + [NullAllowed] + string HelpAnchor { get; } + + /// Sets the informative text displayed in the panel. + /// The informative text string, or . + [Export ("setInformativeText:")] + void SetInformativeText ([NullAllowed] string informativeText); + + /// Gets the informative text displayed in the panel. + [Export ("informativeText")] + [NullAllowed] + string InformativeText { get; } + + /// Sets the domain string used to filter identities. + /// The domain string, or . + [Export ("setDomain:")] + void SetDomain ([NullAllowed] string domainString); + + /// Gets the domain string used to filter identities. + [Export ("domain")] + [NullAllowed] + string Domain { get; } + } + + /// A table cell view used in the identity chooser panel to display identity and issuer information. + [BaseType (typeof (NSTableCellView))] + interface SFChooseIdentityTableCellView { + + /// Initializes the cell view with the specified frame rectangle. + /// The frame rectangle for the cell view. + [Export ("initWithFrame:")] + NativeHandle Constructor (CoreGraphics.CGRect frameRect); + + /// Gets or sets the text field that displays the certificate issuer name. + [NullAllowed] + [Export ("issuerTextField", ArgumentSemantic.Assign)] + NSTextField IssuerTextField { get; set; } + } + + /// A save panel for creating a new keychain file. + [BaseType (typeof (NSSavePanel))] + interface SFKeychainSavePanel { + + /// Gets the shared keychain save panel instance. + [Static] + [Export ("sharedKeychainSavePanel")] + SFKeychainSavePanel SharedKeychainSavePanel { get; } + + /// Displays the panel modally starting in the specified directory with a suggested filename. + /// The directory to start in, or for the default. + /// The suggested filename, or . + /// The button code that was pressed to dismiss the panel. + [Export ("runModalForDirectory:file:")] + nint RunModalForDirectory ([NullAllowed] string path, [NullAllowed] string name); + + /// Sets the password for the new keychain. + /// The password to use, or . + [Export ("setPassword:")] + void SetPassword ([NullAllowed] string password); + + /// Gets the keychain that was created, or if the user cancelled. + [Export ("keychain")] + [NullAllowed] + SecKeychain Keychain { get; } + + /// Gets the last error that occurred during keychain creation, or if no error. + [Export ("error")] + [NullAllowed] + NSError Error { get; } + + /// Displays the panel as a sheet starting in the specified directory with a suggested filename. + /// The directory to start in, or for the default. + /// The suggested filename, or . + /// The window to which the sheet is attached. + /// The delegate that receives the did-end callback, or . + /// The selector invoked when the sheet ends, or . + /// A pointer to context information passed to the callback. + [Export ("beginSheetForDirectory:file:modalForWindow:modalDelegate:didEndSelector:contextInfo:")] + void BeginSheet ([NullAllowed] string path, [NullAllowed] string name, NSWindow docWindow, [NullAllowed] NSObject modalDelegate, [NullAllowed] Selector didEndSelector, IntPtr contextInfo); + } + + /// A panel for editing keychain settings such as lock-on-sleep and auto-lock interval. + [BaseType (typeof (NSPanel))] + interface SFKeychainSettingsPanel { + + /// Gets the shared keychain settings panel instance. + [Static] + [Export ("sharedKeychainSettingsPanel")] + SFKeychainSettingsPanel SharedKeychainSettingsPanel { get; } + + /// Displays the panel modally for the specified keychain and settings. + /// The to edit. + /// The whose settings to edit. + /// The button code that was pressed to dismiss the panel. + [Export ("runModalForSettings:keychain:")] + nint RunModalForSettings (ref SecKeychainSettings settings, SecKeychain keychain); + + /// Displays the panel as a sheet for editing the specified keychain settings. + /// The window to which the sheet is attached. + /// The delegate that receives the did-end callback, or . + /// The selector invoked when the sheet ends, or . + /// A pointer to context information passed to the callback. + /// The to edit. + /// The whose settings to edit. + [Export ("beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:settings:keychain:")] + void BeginSheet (NSWindow docWindow, [NullAllowed] NSObject modalDelegate, [NullAllowed] Selector didEndSelector, IntPtr contextInfo, ref SecKeychainSettings settings, SecKeychain keychain); + } + + /// Contains keys for the user information dictionary passed to authorization plugin views. + [Static] + interface SFAuthorizationPluginViewKeys { + + /// Key for the user name value in the user information dictionary. + [Field ("SFAuthorizationPluginViewUserNameKey")] + NSString UserNameKey { get; } + + /// Key for the user short name value in the user information dictionary. + [Field ("SFAuthorizationPluginViewUserShortNameKey")] + NSString UserShortNameKey { get; } + } + + /// Contains exception names raised by authorization plugin views. + [Static] + interface SFAuthorizationPluginViewExceptions { + + /// The name of the exception raised when an authorization plugin view cannot be displayed. + [Field ("SFDisplayViewException")] + NSString DisplayViewException { get; } + } +} diff --git a/tests/cecil-tests/ApiTest.KnownFailures.cs b/tests/cecil-tests/ApiTest.KnownFailures.cs index 2127b451fa98..3cc36f1e7352 100644 --- a/tests/cecil-tests/ApiTest.KnownFailures.cs +++ b/tests/cecil-tests/ApiTest.KnownFailures.cs @@ -582,12 +582,14 @@ public partial class ApiTest { "SearchKit.SKSearch..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "SearchKit.SKSummary..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.Authorization..ctor(ObjCRuntime.NativeHandle, System.Boolean)", + "Security.AuthorizationEngine..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.SecAccessControl..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.SecCertificate..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.SecCertificate2..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.SecIdentity..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.SecIdentity2..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.SecKey..ctor(ObjCRuntime.NativeHandle, System.Boolean)", + "Security.SecKeychain..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.SecPolicy..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.SecProtocolMetadata..ctor(ObjCRuntime.NativeHandle, System.Boolean)", "Security.SecProtocolOptions..ctor(ObjCRuntime.NativeHandle, System.Boolean)", diff --git a/tests/cecil-tests/ConstructorTest.cs b/tests/cecil-tests/ConstructorTest.cs index e8e13fc18598..1e869f26c565 100644 --- a/tests/cecil-tests/ConstructorTest.cs +++ b/tests/cecil-tests/ConstructorTest.cs @@ -306,6 +306,7 @@ public void INativeObjectIntPtrConstructorDoesNotOwnHandle (ApplePlatform platfo case "Protocol": // not really refcounted case "AURenderEventEnumerator": // this class shouldn't really be an INativeObject in the first place case "AudioBuffers": // this class shouldn't really be an INativeObject in the first place + case "AuthorizationCallbacks": // non-owning wrapper over a borrowed callback struct pointer case "AVAudioChannelLayout": // has a private IntPtr constructor which is a void* in native code (i.e. not a mistake). case "VNVideoProcessorFrameRateCadence": // has a nint (i.e. IntPtr) constructor (framerate) - not a mistake case "NSMutableOrderedSet`1": diff --git a/tests/cecil-tests/Documentation.KnownFailures.txt b/tests/cecil-tests/Documentation.KnownFailures.txt index 2a3ae6cf7f27..6ad9d7c2b958 100644 --- a/tests/cecil-tests/Documentation.KnownFailures.txt +++ b/tests/cecil-tests/Documentation.KnownFailures.txt @@ -16113,6 +16113,9 @@ M:Security.SslContext.Read(System.Byte[],System.IntPtr@) M:Security.SslContext.Write(System.Byte[],System.IntPtr@) M:Security.SslStreamConnection.Read(System.IntPtr,System.IntPtr@) M:Security.SslStreamConnection.Write(System.IntPtr,System.IntPtr@) +M:SecurityInterface.AuthorizationRights.Finalize +M:SecurityInterface.SFAuthorizationView.Dispose(System.Boolean) +M:SecurityInterface.SFChooseIdentityTableCellView.Dispose(System.Boolean) M:SecurityUI.SFCertificatePresentation.#ctor(Security.SecTrust) M:SecurityUI.SFCertificatePresentation.DismissSheet M:SecurityUI.SFCertificatePresentation.PresentSheet(AppKit.NSWindow,System.Action) diff --git a/tests/cecil-tests/HandleSafety.KnownFailures.cs b/tests/cecil-tests/HandleSafety.KnownFailures.cs index 403ad70a55bd..e7e5f38cb680 100644 --- a/tests/cecil-tests/HandleSafety.KnownFailures.cs +++ b/tests/cecil-tests/HandleSafety.KnownFailures.cs @@ -200,6 +200,15 @@ public partial class HandleSafetyTest { "Security.SecRecord.set_Label (System.String)", "Security.SecRecord.set_TokenID (Security.SecTokenID)", "Security.SslContext.Bundle (Security.SecIdentity, System.Collections.Generic.IEnumerable`1)", + "SecurityInterface.AuthorizationCallbacks.DidDeactivate (Security.AuthorizationEngine)", + "SecurityInterface.AuthorizationCallbacks.GetContextValue (Security.AuthorizationEngine, System.String, SecurityInterface.AuthorizationContextFlags&, System.Byte[]&)", + "SecurityInterface.AuthorizationCallbacks.GetHintValue (Security.AuthorizationEngine, System.String, System.Byte[]&)", + "SecurityInterface.AuthorizationCallbacks.RemoveContextValue (Security.AuthorizationEngine, System.String)", + "SecurityInterface.AuthorizationCallbacks.RemoveHintValue (Security.AuthorizationEngine, System.String)", + "SecurityInterface.AuthorizationCallbacks.RequestInterrupt (Security.AuthorizationEngine)", + "SecurityInterface.AuthorizationCallbacks.SetContextValue (Security.AuthorizationEngine, System.String, SecurityInterface.AuthorizationContextFlags, System.Byte[])", + "SecurityInterface.AuthorizationCallbacks.SetHintValue (Security.AuthorizationEngine, System.String, System.Byte[])", + "SecurityInterface.AuthorizationCallbacks.SetResult (Security.AuthorizationEngine, SecurityInterface.AuthorizationResult)", "System.Boolean ObjCRuntime.DisposableObject.op_Equality (ObjCRuntime.DisposableObject, ObjCRuntime.DisposableObject)", "System.Boolean ObjCRuntime.DisposableObject.op_Inequality (ObjCRuntime.DisposableObject, ObjCRuntime.DisposableObject)", "System.Boolean ObjCRuntime.NativeHandle.op_Equality (System.IntPtr, ObjCRuntime.NativeHandle)", diff --git a/tests/cecil-tests/HandleSafety.cs b/tests/cecil-tests/HandleSafety.cs index b9957120e483..b3642b0f393e 100644 --- a/tests/cecil-tests/HandleSafety.cs +++ b/tests/cecil-tests/HandleSafety.cs @@ -174,6 +174,8 @@ static bool IsUnsafeMethodCall (MethodReference? target) case "CoreGraphics.CGPDFObject": // just a wrapper around a pointer, doesn't free anything in its destructor case "CoreText.CTRunDelegateOperations": // The Handle property is a GCHandle (converted to IntPtr) case "CoreGraphics.CGEvent/TapData": // The Handle property is a GCHandle (converted to IntPtr) + case "SecurityInterface.AuthorizationCallbacks": // non-owning wrapper around a borrowed callback struct pointer + case "SecurityInterface.AuthorizationRights": // IDisposable wrapper that manages its own unmanaged memory switch (target.Name) { case "get_Handle": return false; diff --git a/tests/dotnet/UnitTests/ProjectTest.cs b/tests/dotnet/UnitTests/ProjectTest.cs index db0634f8b81e..ec355f13b81a 100644 --- a/tests/dotnet/UnitTests/ProjectTest.cs +++ b/tests/dotnet/UnitTests/ProjectTest.cs @@ -3405,6 +3405,7 @@ public void AppendRuntimeIdentifierToOutputPath_DisableDirectoryBuildProps (Appl "/System/Library/Frameworks/ScreenTime.framework/Versions/A/ScreenTime", "/System/Library/Frameworks/ScriptingBridge.framework/Versions/A/ScriptingBridge", "/System/Library/Frameworks/Security.framework/Versions/A/Security", + "/System/Library/Frameworks/SecurityInterface.framework/Versions/A/SecurityInterface", "/System/Library/Frameworks/SecurityUI.framework/Versions/A/SecurityUI", "/System/Library/Frameworks/SensitiveContentAnalysis.framework/Versions/A/SensitiveContentAnalysis", "/System/Library/Frameworks/ServiceManagement.framework/Versions/A/ServiceManagement", diff --git a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-size.txt b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-size.txt index f991490a3294..3a270a941c4b 100644 --- a/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-size.txt +++ b/tests/dotnet/UnitTests/expected/MacOSX-CoreCLR-Interpreter-size.txt @@ -1,10 +1,10 @@ -AppBundleSize: 247,357,169 bytes (241,559.7 KB = 235.9 MB) +AppBundleSize: 247,497,750 bytes (241,697.0 KB = 236.0 MB) # The following list of files and their sizes is just informational / for review, and isn't used in the test: Contents/_CodeSignature/CodeResources: 67,160 bytes (65.6 KB = 0.1 MB) -Contents/Info.plist: 725 bytes (0.7 KB = 0.0 MB) -Contents/MacOS/SizeTestApp: 8,001,536 bytes (7,814.0 KB = 7.6 MB) +Contents/Info.plist: 746 bytes (0.7 KB = 0.0 MB) +Contents/MacOS/SizeTestApp: 8,003,856 bytes (7,816.3 KB = 7.6 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.CSharp.dll: 893,200 bytes (872.3 KB = 0.9 MB) -Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll: 36,723,712 bytes (35,863.0 KB = 35.0 MB) +Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.macOS.dll: 36,792,832 bytes (35,930.5 KB = 35.1 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.Core.dll: 1,335,048 bytes (1,303.8 KB = 1.3 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.VisualBasic.dll: 17,712 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/Microsoft.Win32.Primitives.dll: 16,144 bytes (15.8 KB = 0.0 MB) @@ -178,7 +178,7 @@ Contents/MonoBundle/.xamarin/osx-arm64/System.Xml.XPath.dll: 16,136 bytes (15.8 Contents/MonoBundle/.xamarin/osx-arm64/System.Xml.XPath.XDocument.dll: 17,672 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-arm64/WindowsBase.dll: 16,656 bytes (16.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.CSharp.dll: 796,432 bytes (777.8 KB = 0.8 MB) -Contents/MonoBundle/.xamarin/osx-x64/Microsoft.macOS.dll: 36,723,712 bytes (35,863.0 KB = 35.0 MB) +Contents/MonoBundle/.xamarin/osx-x64/Microsoft.macOS.dll: 36,792,832 bytes (35,930.5 KB = 35.1 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.Core.dll: 1,166,600 bytes (1,139.3 KB = 1.1 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.VisualBasic.dll: 17,720 bytes (17.3 KB = 0.0 MB) Contents/MonoBundle/.xamarin/osx-x64/Microsoft.Win32.Primitives.dll: 16,144 bytes (15.8 KB = 0.0 MB) diff --git a/tests/monotouch-test/SecurityInterface/AuthorizationManualBindingsTest.cs b/tests/monotouch-test/SecurityInterface/AuthorizationManualBindingsTest.cs new file mode 100644 index 000000000000..353e16dd2979 --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/AuthorizationManualBindingsTest.cs @@ -0,0 +1,200 @@ +#if __MACOS__ +using System; +using System.Runtime.InteropServices; +using NUnit.Framework; +using ObjCRuntime; +using Security; +using SecurityInterface; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class AuthorizationEngineTest { + + [Test] + public void Create_Zero_ReturnsNull () + { + var engine = AuthorizationEngine.Create (NativeHandle.Zero); + Assert.That (engine, Is.Null, "Zero handle should return null"); + } + } + + [TestFixture] + [Preserve (AllMembers = true)] + public unsafe class AuthorizationCallbacksTest { + + [StructLayout (LayoutKind.Sequential)] + struct FakeCallbacksNative { + public uint Version; + // All remaining fields are function pointers — set to zero for testing + public IntPtr SetResult; + public IntPtr RequestInterrupt; + public IntPtr DidDeactivate; + public IntPtr GetContextValue; + public IntPtr SetContextValue; + public IntPtr GetHintValue; + public IntPtr SetHintValue; + public IntPtr GetArguments; + public IntPtr GetSessionId; + public IntPtr GetImmutableHintValue; + public IntPtr GetLAContext; + public IntPtr GetTokenIdentities; + public IntPtr GetTKTokenWatcher; + public IntPtr RemoveHintValue; + public IntPtr RemoveContextValue; + } + + [Test] + public void Create_Zero_StoresHandle () + { + var callbacks = new AuthorizationCallbacks (NativeHandle.Zero); + Assert.That ((IntPtr) callbacks.Handle, Is.EqualTo (IntPtr.Zero), "Zero handle should be stored"); + } + + [Test] + public void Create_NonZero_ReturnsWrapper () + { + var native = new FakeCallbacksNative { Version = 7 }; + var ptr = Marshal.AllocHGlobal (Marshal.SizeOf ()); + try { + Marshal.StructureToPtr (native, ptr, false); + var callbacks = new AuthorizationCallbacks (ptr); + Assert.That (callbacks, Is.Not.Null, "Should create a wrapper"); + Assert.That (callbacks.Handle, Is.EqualTo ((NativeHandle) ptr), "Handle should match"); + } finally { + Marshal.FreeHGlobal (ptr); + } + } + + [Test] + public void Version_ReadsCorrectly () + { + var native = new FakeCallbacksNative { Version = 42 }; + var ptr = Marshal.AllocHGlobal (Marshal.SizeOf ()); + try { + Marshal.StructureToPtr (native, ptr, false); + var callbacks = new AuthorizationCallbacks (ptr); + Assert.That (callbacks.Version, Is.EqualTo (42u), "Version should read 42"); + } finally { + Marshal.FreeHGlobal (ptr); + } + } + + [Test] + public void Version_DifferentValues () + { + var native = new FakeCallbacksNative { Version = 1 }; + var ptr = Marshal.AllocHGlobal (Marshal.SizeOf ()); + try { + Marshal.StructureToPtr (native, ptr, false); + var callbacks = new AuthorizationCallbacks (ptr); + Assert.That (callbacks.Version, Is.EqualTo (1u), "Version should be 1"); + + // Update the native memory and verify the wrapper reads the new value + native.Version = 99; + Marshal.StructureToPtr (native, ptr, false); + Assert.That (callbacks.Version, Is.EqualTo (99u), "Version should update to 99"); + } finally { + Marshal.FreeHGlobal (ptr); + } + } + } + + [TestFixture] + [Preserve (AllMembers = true)] + public class AuthorizationRightsRoundTripTest { + + [Test] + public void FromHandle_NullReturnsNull () + { + var rights = AuthorizationRights.FromHandle (NativeHandle.Zero); + Assert.That (rights, Is.Null, "Zero handle should return null"); + } + + [Test] + public void NativeHandle_RoundTrip () + { + // Create rights, get their handle, then read them back via FromHandle + using var original = new AuthorizationRights ( + new AuthorizationRight ("com.example.right1", new byte [] { 0xAA, 0xBB }, 5), + new AuthorizationRight ("com.example.right2") + ); + + var handle = original.Handle; + Assert.That (handle, Is.Not.EqualTo (NativeHandle.Zero), "Handle should be valid"); + + // Read back from the same handle (simulates what the bgen getter does) + using var readBack = AuthorizationRights.FromHandle (handle); + Assert.That (readBack, Is.Not.Null, "FromHandle should return non-null"); + Assert.That (readBack!.Count, Is.EqualTo (2), "Count should match"); + + Assert.That (readBack [0].Name, Is.EqualTo ("com.example.right1"), "Name[0]"); + Assert.That (readBack [0].Value, Is.EqualTo (new byte [] { 0xAA, 0xBB }), "Value[0]"); + Assert.That (readBack [0].Flags, Is.EqualTo (5u), "Flags[0]"); + + Assert.That (readBack [1].Name, Is.EqualTo ("com.example.right2"), "Name[1]"); + Assert.That (readBack [1].Value, Is.Null, "Value[1] should be null"); + Assert.That (readBack [1].Flags, Is.EqualTo (0u), "Flags[1]"); + } + + [Test] + public void LargeRightsSet () + { + var items = new AuthorizationRight [100]; + for (int i = 0; i < 100; i++) + items [i] = new AuthorizationRight ($"com.example.right{i}", new byte [] { (byte) i }, (uint) i); + + using var rights = new AuthorizationRights (items); + Assert.That (rights.Count, Is.EqualTo (100), "Count"); + + using var readBack = AuthorizationRights.FromHandle (rights.Handle); + Assert.That (readBack!.Count, Is.EqualTo (100), "ReadBack Count"); + Assert.That (readBack [50].Name, Is.EqualTo ("com.example.right50"), "Name[50]"); + Assert.That (readBack [50].Value, Is.EqualTo (new byte [] { 50 }), "Value[50]"); + Assert.That (readBack [50].Flags, Is.EqualTo (50u), "Flags[50]"); + } + + [Test] + public void UnicodeRightNames () + { + using var rights = new AuthorizationRights ( + new AuthorizationRight ("com.example.日本語テスト"), + new AuthorizationRight ("com.example.émojis🎉") + ); + + using var readBack = AuthorizationRights.FromHandle (rights.Handle); + Assert.That (readBack! [0].Name, Is.EqualTo ("com.example.日本語テスト"), "Unicode name[0]"); + Assert.That (readBack [1].Name, Is.EqualTo ("com.example.émojis🎉"), "Unicode name[1]"); + } + + [Test] + public void EmptyValueVsNullValue () + { + using var rights = new AuthorizationRights ( + new AuthorizationRight ("with-empty", new byte [0]), + new AuthorizationRight ("with-null", null) + ); + + using var readBack = AuthorizationRights.FromHandle (rights.Handle); + Assert.That (readBack! [0].Value, Is.Null, "Empty byte array should read back as null (zero length)"); + Assert.That (readBack [1].Value, Is.Null, "Null value should remain null"); + } + } + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFAuthorizationPluginViewManualTest { + + [Test] + public void SFAuthorizationPluginView_CallbacksProperty_Null () + { + // We can't construct a real SFAuthorizationPluginView without a valid engine+callbacks + // from the authorization plugin host. But we can verify the type exists and is constructible + // via the ObjC runtime by checking the class handle. + var classHandle = global::ObjCRuntime.Class.GetHandle ("SFAuthorizationPluginView"); + Assert.That (classHandle, Is.Not.EqualTo (IntPtr.Zero), "SFAuthorizationPluginView ObjC class should exist"); + } + } +} +#endif // __MACOS__ diff --git a/tests/monotouch-test/SecurityInterface/AuthorizationRightsTest.cs b/tests/monotouch-test/SecurityInterface/AuthorizationRightsTest.cs new file mode 100644 index 000000000000..e911c998c503 --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/AuthorizationRightsTest.cs @@ -0,0 +1,99 @@ +#if __MACOS__ +using System; +using NUnit.Framework; +using SecurityInterface; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class AuthorizationRightsTest { + + [Test] + public void Create_FromStrings () + { + using var rights = new AuthorizationRights ("com.example.right1", "com.example.right2"); + Assert.That (rights.Count, Is.EqualTo (2), "Count"); + Assert.That (rights [0].Name, Is.EqualTo ("com.example.right1"), "Name[0]"); + Assert.That (rights [1].Name, Is.EqualTo ("com.example.right2"), "Name[1]"); + Assert.That (rights.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle"); + } + + [Test] + public void Create_FromAuthorizationRight () + { + var right = new AuthorizationRight ("com.example.test", new byte [] { 1, 2, 3 }, 42); + using var rights = new AuthorizationRights (right); + Assert.That (rights.Count, Is.EqualTo (1), "Count"); + Assert.That (rights [0].Name, Is.EqualTo ("com.example.test"), "Name"); + Assert.That (rights [0].Value, Is.EqualTo (new byte [] { 1, 2, 3 }), "Value"); + Assert.That (rights [0].Flags, Is.EqualTo (42u), "Flags"); + } + + [Test] + public void Create_Empty () + { + using var rights = new AuthorizationRights (Array.Empty ()); + Assert.That (rights.Count, Is.EqualTo (0), "Count"); + Assert.That (rights.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle should be valid even when empty"); + } + + [Test] + public void AuthorizationRight_NullValue () + { + var right = new AuthorizationRight ("com.example.novalue"); + Assert.That (right.Name, Is.EqualTo ("com.example.novalue"), "Name"); + Assert.That (right.Value, Is.Null, "Value should be null"); + Assert.That (right.Flags, Is.EqualTo (0u), "Flags should default to 0"); + } + + [Test] + public void AuthorizationRight_ValueIsCopied () + { + var original = new byte [] { 10, 20, 30 }; + var right = new AuthorizationRight ("test", original); + original [0] = 99; + Assert.That (right.Value! [0], Is.EqualTo (10), "Value should be a copy, not a reference"); + } + + [Test] + public void AuthorizationRight_NullName_Throws () + { + Assert.Throws (() => new AuthorizationRight (null!)); + } + + [Test] + public void Dispose_ClearsHandle () + { + var rights = new AuthorizationRights ("test"); + Assert.That (rights.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle before dispose"); + rights.Dispose (); + Assert.That ((IntPtr) rights.Handle, Is.EqualTo (IntPtr.Zero), "Handle after dispose"); + } + + [Test] + public void Dispose_CanBeCalledMultipleTimes () + { + var rights = new AuthorizationRights ("test"); + rights.Dispose (); + Assert.DoesNotThrow (() => rights.Dispose (), "Double dispose should not throw"); + } + + [Test] + public void Enumeration () + { + using var rights = new AuthorizationRights ("a", "b", "c"); + var names = new global::System.Collections.Generic.List (); + foreach (var right in rights) + names.Add (right.Name); + Assert.That (names, Is.EqualTo (new [] { "a", "b", "c" }), "Enumeration"); + } + + [Test] + public void NullStrings_Throws () + { + Assert.Throws (() => new AuthorizationRights ((string []) null!)); + } + } +} +#endif // __MACOS__ diff --git a/tests/monotouch-test/SecurityInterface/SFAuthorizationViewTest.cs b/tests/monotouch-test/SecurityInterface/SFAuthorizationViewTest.cs new file mode 100644 index 000000000000..7a7850b12cb8 --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/SFAuthorizationViewTest.cs @@ -0,0 +1,93 @@ +#if __MACOS__ +using System; +using NUnit.Framework; +using AppKit; +using Foundation; +using Security; +using SecurityInterface; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFAuthorizationViewTest { + + [Test] + public void Constructor () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + Assert.That (view.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle"); + } + + [Test] + public void AuthorizationState_InitialValue () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + var state = view.AuthorizationState; + Assert.That (state, Is.EqualTo (SFAuthorizationViewState.Startup), "Initial state should be Startup"); + } + + [Test] + public void IsEnabled_Default () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + // The default enabled state depends on the system, just verify it doesn't crash + var _ = view.IsEnabled; + } + + [Test] + public void SetEnabled () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + Assert.DoesNotThrow (() => view.SetEnabled (false), "SetEnabled false"); + Assert.DoesNotThrow (() => view.SetEnabled (true), "SetEnabled true"); + } + + [Test] + public void SetAuthorizationString () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + Assert.DoesNotThrow (() => view.SetAuthorizationString ("com.example.test"), "SetAuthorizationString"); + } + + [Test] + public void SetAuthorizationString_NullThrows () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + Assert.Throws (() => view.SetAuthorizationString (null!)); + } + + [Test] + public void AuthorizationRightsSet_Get_InitiallyNull () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + var rights = view.AuthorizationRightsSet; + // Rights may or may not be null depending on initialization state + } + + [Test] + public void Delegate_SetAndGet () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + Assert.That (view.WeakDelegate, Is.Null, "Delegate should initially be null"); + view.WeakDelegate = NSObject.FromObject ("test"); + Assert.That (view.WeakDelegate, Is.Not.Null, "Delegate should be set"); + } + + [Test] + public void SetFlags () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + Assert.DoesNotThrow (() => view.SetFlags (0), "SetFlags 0"); + } + + [Test] + public void SetAutoupdate () + { + using var view = new SFAuthorizationView (new global::CoreGraphics.CGRect (0, 0, 100, 100)); + Assert.DoesNotThrow (() => view.SetAutoupdate (false), "SetAutoupdate false"); + Assert.DoesNotThrow (() => view.SetAutoupdate (true, 60.0), "SetAutoupdate with interval"); + } + } +} +#endif // __MACOS__ diff --git a/tests/monotouch-test/SecurityInterface/SFCertificatePanelTest.cs b/tests/monotouch-test/SecurityInterface/SFCertificatePanelTest.cs new file mode 100644 index 000000000000..3e3fbbc18555 --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/SFCertificatePanelTest.cs @@ -0,0 +1,74 @@ +#if __MACOS__ +using System; +using NUnit.Framework; +using AppKit; +using Foundation; +using Security; +using SecurityInterface; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFCertificatePanelTest { + + [Test] + public void SharedCertificatePanel () + { + var panel = SFCertificatePanel.SharedCertificatePanel; + Assert.That (panel, Is.Not.Null, "SharedCertificatePanel should not be null"); + Assert.That (panel.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle"); + } + + [Test] + public void CertificateView () + { + // Accessing the panel's CertificateView triggers NSView hierarchy initialization + // which causes a CF_IS_OBJC breakpoint trap on headless CI machines, hanging the process. + TestRuntime.IgnoreInCI ("SFCertificatePanel.CertificateView triggers view hierarchy init that hangs on headless CI."); + var panel = SFCertificatePanel.SharedCertificatePanel; + var view = panel.CertificateView; + } + + [Test] + public void Properties () + { + // Panel property setters may trigger deferred UI operations on headless CI. + TestRuntime.IgnoreInCI ("SFCertificatePanel property setters may trigger UI operations on headless CI."); + var panel = SFCertificatePanel.SharedCertificatePanel; + + Assert.DoesNotThrow (() => panel.SetShowsHelp (false), "SetShowsHelp"); + Assert.That (panel.ShowsHelp, Is.False, "ShowsHelp"); + + Assert.DoesNotThrow (() => panel.SetDefaultButtonTitle ("OK"), "SetDefaultButtonTitle"); + Assert.DoesNotThrow (() => panel.SetAlternateButtonTitle ("Cancel"), "SetAlternateButtonTitle"); + + panel.SetHelpAnchor ("testAnchor"); + Assert.That (panel.HelpAnchor, Is.EqualTo ("testAnchor"), "HelpAnchor round-trip"); + } + } + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFCertificateTrustPanelTest { + + [Test] + public void SharedCertificateTrustPanel () + { + var panel = SFCertificateTrustPanel.SharedCertificateTrustPanel; + Assert.That (panel, Is.Not.Null, "SharedCertificateTrustPanel should not be null"); + Assert.That (panel.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle"); + } + + [Test] + public void InformativeText () + { + // Panel property setters may trigger deferred UI operations on headless CI. + TestRuntime.IgnoreInCI ("SFCertificateTrustPanel property setters may trigger UI operations on headless CI."); + var panel = SFCertificateTrustPanel.SharedCertificateTrustPanel; + panel.SetInformativeText ("Test informative text"); + Assert.That (panel.InformativeText, Is.EqualTo ("Test informative text"), "InformativeText round-trip"); + } + } +} +#endif // __MACOS__ diff --git a/tests/monotouch-test/SecurityInterface/SFCertificateViewTest.cs b/tests/monotouch-test/SecurityInterface/SFCertificateViewTest.cs new file mode 100644 index 000000000000..d52f13c88449 --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/SFCertificateViewTest.cs @@ -0,0 +1,110 @@ +#if __MACOS__ +using System; +using NUnit.Framework; +using AppKit; +using Foundation; +using Security; +using SecurityInterface; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFCertificateViewTest { + + [Test] + public void Constructor () + { + using var view = new SFCertificateView (new global::CoreGraphics.CGRect (0, 0, 300, 200)); + Assert.That (view.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle"); + } + + [Test] + public void GetCertificate_InitiallyNull () + { + using var view = new SFCertificateView (new global::CoreGraphics.CGRect (0, 0, 300, 200)); + var cert = view.GetCertificate (); + Assert.That (cert, Is.Null, "Certificate should initially be null"); + } + + [Test] + public void SetCertificate_Null () + { + using var view = new SFCertificateView (new global::CoreGraphics.CGRect (0, 0, 300, 200)); + Assert.DoesNotThrow (() => view.SetCertificate (null), "SetCertificate null"); + } + + [Test] + public void SetCertificate_Valid () + { + using var view = new SFCertificateView (new global::CoreGraphics.CGRect (0, 0, 300, 200)); + using var data = NSData.FromArray (CertificateData.AppleComCert); + using var cert = new SecCertificate (data); + view.SetCertificate (cert); + + var retrieved = view.GetCertificate (); + Assert.That (retrieved, Is.Not.Null, "Should get certificate back"); + retrieved!.Dispose (); + } + + [Test] + public void Properties () + { + using var view = new SFCertificateView (new global::CoreGraphics.CGRect (0, 0, 300, 200)); + + Assert.DoesNotThrow (() => view.SetEditableTrust (false), "SetEditableTrust"); + Assert.DoesNotThrow (() => { var _ = view.IsEditable; }, "IsEditable"); + + Assert.DoesNotThrow (() => view.SetDisplayTrust (true), "SetDisplayTrust"); + Assert.DoesNotThrow (() => { var _ = view.IsTrustDisplayed; }, "IsTrustDisplayed"); + + Assert.DoesNotThrow (() => view.SetDisplayDetails (true), "SetDisplayDetails"); + Assert.DoesNotThrow (() => { var _ = view.DetailsDisplayed; }, "DetailsDisplayed"); + + Assert.DoesNotThrow (() => view.SetDetailsDisclosed (false), "SetDetailsDisclosed"); + Assert.DoesNotThrow (() => { var _ = view.DetailsDisclosed; }, "DetailsDisclosed"); + + Assert.DoesNotThrow (() => view.SetPoliciesDisclosed (false), "SetPoliciesDisclosed"); + Assert.DoesNotThrow (() => { var _ = view.PoliciesDisclosed; }, "PoliciesDisclosed"); + } + + [Test] + public void DisclosureStateDidChangeNotification_Exists () + { + var notification = SFCertificateView.DisclosureStateDidChangeNotification; + Assert.That (notification, Is.Not.Null, "Notification should not be null"); + Assert.That ((int) notification.Length, Is.GreaterThan (0), "Notification string should not be empty"); + } + } + + static class CertificateData { + // Valid self-signed EC P-256 DER certificate (CN=TestCert, valid 10 years) + public static readonly byte [] AppleComCert = { + 0x30, 0x82, 0x01, 0x7b, 0x30, 0x82, 0x01, 0x21, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x7d, + 0x74, 0x9f, 0x84, 0xa4, 0x7d, 0xe9, 0x76, 0x2c, 0xfe, 0x00, 0x77, 0x4d, 0xf3, 0xc4, 0x5e, 0xdd, + 0x0d, 0x0b, 0xa4, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, + 0x13, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x08, 0x54, 0x65, 0x73, 0x74, + 0x43, 0x65, 0x72, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x36, 0x30, 0x34, 0x31, 0x31, 0x30, 0x32, + 0x33, 0x36, 0x31, 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x36, 0x30, 0x34, 0x30, 0x38, 0x30, 0x32, 0x33, + 0x36, 0x31, 0x30, 0x5a, 0x30, 0x13, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x08, 0x54, 0x65, 0x73, 0x74, 0x43, 0x65, 0x72, 0x74, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, + 0x03, 0x42, 0x00, 0x04, 0xc0, 0xc5, 0xf7, 0x7d, 0xb9, 0x5e, 0x44, 0x11, 0x73, 0xb6, 0x27, 0xdb, + 0xaf, 0xdb, 0x6c, 0xa3, 0x40, 0x03, 0xb2, 0xb7, 0x23, 0xf0, 0xdd, 0xd3, 0x6a, 0xe7, 0xa0, 0x89, + 0x75, 0xfa, 0xcb, 0x9f, 0xcb, 0xc3, 0x99, 0x1e, 0x40, 0xde, 0x1d, 0x24, 0x6b, 0xda, 0xd7, 0x65, + 0xfd, 0x9b, 0xff, 0x84, 0x0b, 0xef, 0x5a, 0xd7, 0xec, 0xc9, 0x10, 0x88, 0x57, 0x0f, 0x5b, 0xbc, + 0x69, 0x26, 0x9d, 0xd4, 0xa3, 0x53, 0x30, 0x51, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, + 0x16, 0x04, 0x14, 0xbd, 0xfe, 0xc1, 0xd4, 0xb9, 0x00, 0xe8, 0x0b, 0x67, 0xd8, 0xe6, 0xbc, 0x26, + 0x54, 0x76, 0x4f, 0xe0, 0x95, 0xcb, 0xc0, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, + 0x30, 0x16, 0x80, 0x14, 0xbd, 0xfe, 0xc1, 0xd4, 0xb9, 0x00, 0xe8, 0x0b, 0x67, 0xd8, 0xe6, 0xbc, + 0x26, 0x54, 0x76, 0x4f, 0xe0, 0x95, 0xcb, 0xc0, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, + 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, + 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21, 0x00, 0xf9, 0xb6, 0x9d, + 0x20, 0x4c, 0x71, 0xc4, 0x4f, 0xa1, 0xbb, 0x2e, 0x0d, 0x68, 0x09, 0x2a, 0x0c, 0xdd, 0x78, 0xa2, + 0x62, 0xa8, 0x73, 0x5e, 0xd9, 0x26, 0x37, 0x83, 0x25, 0x44, 0x26, 0x01, 0xbd, 0x02, 0x20, 0x4e, + 0x2a, 0x75, 0x1d, 0x90, 0xde, 0xc6, 0x3b, 0xf7, 0xd0, 0xf4, 0x62, 0x8b, 0x66, 0x87, 0x06, 0xea, + 0x6c, 0xfa, 0xda, 0x25, 0xe3, 0xf6, 0xba, 0x15, 0x47, 0xe5, 0xf2, 0x4e, 0x99, 0xce, 0x9f, + }; + } +} +#endif // __MACOS__ diff --git a/tests/monotouch-test/SecurityInterface/SFChooseIdentityPanelTest.cs b/tests/monotouch-test/SecurityInterface/SFChooseIdentityPanelTest.cs new file mode 100644 index 000000000000..da203c7743c3 --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/SFChooseIdentityPanelTest.cs @@ -0,0 +1,84 @@ +#if __MACOS__ +using System; +using NUnit.Framework; +using AppKit; +using Foundation; +using Security; +using SecurityInterface; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFChooseIdentityPanelTest { + + [Test] + public void SharedChooseIdentityPanel () + { + var panel = SFChooseIdentityPanel.SharedChooseIdentityPanel; + Assert.That (panel, Is.Not.Null, "SharedChooseIdentityPanel should not be null"); + Assert.That (panel.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle"); + } + + [Test] + public void Properties () + { + // Panel property setters may trigger deferred UI operations on headless CI. + TestRuntime.IgnoreInCI ("SFChooseIdentityPanel property setters may trigger UI operations on headless CI."); + var panel = SFChooseIdentityPanel.SharedChooseIdentityPanel; + + Assert.DoesNotThrow (() => panel.SetShowsHelp (true), "SetShowsHelp"); + Assert.That (panel.ShowsHelp, Is.True, "ShowsHelp"); + + panel.SetDefaultButtonTitle ("Select"); + panel.SetAlternateButtonTitle ("Cancel"); + + panel.SetHelpAnchor ("identityHelp"); + Assert.That (panel.HelpAnchor, Is.EqualTo ("identityHelp"), "HelpAnchor round-trip"); + + panel.SetInformativeText ("Choose an identity"); + Assert.That (panel.InformativeText, Is.EqualTo ("Choose an identity"), "InformativeText round-trip"); + + panel.SetDomain ("com.example.test"); + Assert.That (panel.Domain, Is.EqualTo ("com.example.test"), "Domain round-trip"); + } + + [Test] + public void Identity_InitiallyNull () + { + var panel = SFChooseIdentityPanel.SharedChooseIdentityPanel; + // Identity is null until a user selects one from the panel + var identity = panel.Identity; + Assert.That (identity, Is.Null, "Identity should be null when no selection made"); + } + } + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFChooseIdentityTableCellViewTest { + + [Test] + public void Constructor () + { + using var cellView = new SFChooseIdentityTableCellView (new global::CoreGraphics.CGRect (0, 0, 200, 44)); + Assert.That (cellView.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle"); + } + + [Test] + public void IssuerTextField_DefaultNull () + { + using var cellView = new SFChooseIdentityTableCellView (new global::CoreGraphics.CGRect (0, 0, 200, 44)); + Assert.That (cellView.IssuerTextField, Is.Null, "IssuerTextField should initially be null"); + } + + [Test] + public void IssuerTextField_SetAndGet () + { + using var cellView = new SFChooseIdentityTableCellView (new global::CoreGraphics.CGRect (0, 0, 200, 44)); + using var textField = new NSTextField (); + cellView.IssuerTextField = textField; + Assert.That (cellView.IssuerTextField, Is.Not.Null, "IssuerTextField should be set"); + } + } +} +#endif // __MACOS__ diff --git a/tests/monotouch-test/SecurityInterface/SFFieldsAndEnumsTest.cs b/tests/monotouch-test/SecurityInterface/SFFieldsAndEnumsTest.cs new file mode 100644 index 000000000000..98078a0204a2 --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/SFFieldsAndEnumsTest.cs @@ -0,0 +1,85 @@ +#if __MACOS__ +using System; +using NUnit.Framework; +using Foundation; +using SecurityInterface; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFAuthorizationPluginViewFieldsTest { + + [Test] + public void UserNameKey () + { + var key = SFAuthorizationPluginViewKeys.UserNameKey; + Assert.That (key, Is.Not.Null, "UserNameKey should not be null"); + Assert.That ((string) key, Is.Not.Empty, "UserNameKey should not be empty"); + } + + [Test] + public void UserShortNameKey () + { + var key = SFAuthorizationPluginViewKeys.UserShortNameKey; + Assert.That (key, Is.Not.Null, "UserShortNameKey should not be null"); + Assert.That ((string) key, Is.Not.Empty, "UserShortNameKey should not be empty"); + } + + [Test] + public void DisplayViewException () + { + var exc = SFAuthorizationPluginViewExceptions.DisplayViewException; + Assert.That (exc, Is.Not.Null, "DisplayViewException should not be null"); + Assert.That ((string) exc, Is.Not.Empty, "DisplayViewException should not be empty"); + } + } + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFEnumsTest { + + [Test] + public void AuthorizationViewState_Values () + { + Assert.That ((int) SFAuthorizationViewState.Startup, Is.EqualTo (0)); + Assert.That ((int) SFAuthorizationViewState.Locked, Is.EqualTo (1)); + Assert.That ((int) SFAuthorizationViewState.InProgress, Is.EqualTo (2)); + Assert.That ((int) SFAuthorizationViewState.Unlocked, Is.EqualTo (3)); + } + + [Test] + public void ButtonType_Values () + { + Assert.That ((int) SFButtonType.Cancel, Is.EqualTo (0)); + Assert.That ((int) SFButtonType.Ok, Is.EqualTo (1)); + Assert.That ((int) SFButtonType.Back, Is.EqualTo (0)); + Assert.That ((int) SFButtonType.Login, Is.EqualTo (1)); + } + + [Test] + public void ViewType_Values () + { + Assert.That ((int) SFViewType.IdentityAndCredentials, Is.EqualTo (0)); + Assert.That ((int) SFViewType.Credentials, Is.EqualTo (1)); + } + + [Test] + public void AuthorizationResult_Values () + { + Assert.That ((uint) AuthorizationResult.Allow, Is.EqualTo (0u)); + Assert.That ((uint) AuthorizationResult.Deny, Is.EqualTo (1u)); + Assert.That ((uint) AuthorizationResult.Undefined, Is.EqualTo (2u)); + Assert.That ((uint) AuthorizationResult.UserCanceled, Is.EqualTo (3u)); + } + + [Test] + public void AuthorizationContextFlags_Values () + { + Assert.That ((uint) AuthorizationContextFlags.Extractable, Is.EqualTo (1u)); + Assert.That ((uint) AuthorizationContextFlags.Volatile, Is.EqualTo (2u)); + Assert.That ((uint) AuthorizationContextFlags.Sticky, Is.EqualTo (4u)); + } + } +} +#endif // __MACOS__ diff --git a/tests/monotouch-test/SecurityInterface/SFKeychainPanelTest.cs b/tests/monotouch-test/SecurityInterface/SFKeychainPanelTest.cs new file mode 100644 index 000000000000..7dbde4ddaa24 --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/SFKeychainPanelTest.cs @@ -0,0 +1,67 @@ +#if __MACOS__ +using System; +using NUnit.Framework; +using AppKit; +using SecurityInterface; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFKeychainSavePanelTest { + + [Test] + public void SharedKeychainSavePanel () + { + var panel = SFKeychainSavePanel.SharedKeychainSavePanel; + Assert.That (panel, Is.Not.Null, "SharedKeychainSavePanel should not be null"); + Assert.That (panel.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle"); + } + + [Test] + public void SetPassword () + { + // SFKeychainSavePanel inherits from NSSavePanel which may require window server access. + TestRuntime.IgnoreInCI ("SFKeychainSavePanel operations may trigger UI on headless CI."); + var panel = SFKeychainSavePanel.SharedKeychainSavePanel; + Assert.DoesNotThrow (() => panel.SetPassword ("test-password"), "SetPassword"); + Assert.DoesNotThrow (() => panel.SetPassword (null), "SetPassword null"); + } + + [Test] + public void Keychain_BeforeCreation () + { + // SFKeychainSavePanel inherits from NSSavePanel which may require window server access. + TestRuntime.IgnoreInCI ("SFKeychainSavePanel operations may trigger UI on headless CI."); + var panel = SFKeychainSavePanel.SharedKeychainSavePanel; + // Keychain is null until a user creates one via the panel + var keychain = panel.Keychain; + // May or may not be null depending on previous panel usage + } + + [Test] + public void Error_BeforeCreation () + { + // SFKeychainSavePanel inherits from NSSavePanel which may require window server access. + TestRuntime.IgnoreInCI ("SFKeychainSavePanel operations may trigger UI on headless CI."); + var panel = SFKeychainSavePanel.SharedKeychainSavePanel; + // Error should be null if no operation has been attempted + var error = panel.Error; + // May or may not be null depending on previous panel usage + } + } + + [TestFixture] + [Preserve (AllMembers = true)] + public class SFKeychainSettingsPanelTest { + + [Test] + public void SharedKeychainSettingsPanel () + { + var panel = SFKeychainSettingsPanel.SharedKeychainSettingsPanel; + Assert.That (panel, Is.Not.Null, "SharedKeychainSettingsPanel should not be null"); + Assert.That (panel.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle"); + } + } +} +#endif // __MACOS__ diff --git a/tests/monotouch-test/SecurityInterface/SecKeychainSettingsTest.cs b/tests/monotouch-test/SecurityInterface/SecKeychainSettingsTest.cs new file mode 100644 index 000000000000..845d3d3086d0 --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/SecKeychainSettingsTest.cs @@ -0,0 +1,61 @@ +#if __MACOS__ +using System; +using System.Runtime.InteropServices; +using NUnit.Framework; +using Security; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class SecKeychainSettingsTest { + + [Test] + public void Create_DefaultValues () + { + var settings = SecKeychainSettings.Create (); + Assert.That (settings.Version, Is.EqualTo (1), "Version should be 1"); + Assert.That (settings.LockOnSleep, Is.False, "LockOnSleep should default to false"); + Assert.That (settings.UseLockInterval, Is.False, "UseLockInterval should default to false"); + Assert.That (settings.LockInterval, Is.EqualTo (0), "LockInterval should default to 0"); + } + + [Test] + public void Properties_RoundTrip () + { + var settings = SecKeychainSettings.Create (); + + settings.LockOnSleep = true; + Assert.That (settings.LockOnSleep, Is.True, "LockOnSleep"); + + settings.UseLockInterval = true; + Assert.That (settings.UseLockInterval, Is.True, "UseLockInterval"); + + settings.LockInterval = 300; + Assert.That (settings.LockInterval, Is.EqualTo (300), "LockInterval"); + + settings.Version = 2; + Assert.That (settings.Version, Is.EqualTo (2), "Version"); + } + + [Test] + public void LockOnSleep_FalseRoundTrip () + { + var settings = SecKeychainSettings.Create (); + settings.LockOnSleep = true; + settings.LockOnSleep = false; + Assert.That (settings.LockOnSleep, Is.False, "LockOnSleep should be false after reset"); + } + + [Test] + public void StructSize_IsBlittable () + { + // SecKeychainSettings has 4 fields: version(4) + lockOnSleep(1) + useLockInterval(1) + lockInterval(4) = 10 + // But with alignment it may be padded + var size = Marshal.SizeOf (); + Assert.That (size, Is.GreaterThanOrEqualTo (10), "Struct should be at least 10 bytes"); + Assert.That (size, Is.LessThanOrEqualTo (16), "Struct should not exceed 16 bytes with padding"); + } + } +} +#endif // __MACOS__ diff --git a/tests/monotouch-test/SecurityInterface/SecKeychainTest.cs b/tests/monotouch-test/SecurityInterface/SecKeychainTest.cs new file mode 100644 index 000000000000..854be2fe7f9d --- /dev/null +++ b/tests/monotouch-test/SecurityInterface/SecKeychainTest.cs @@ -0,0 +1,73 @@ +#if __MACOS__ +using System; +using System.IO; +using NUnit.Framework; +using Security; + +namespace MonoTouchFixtures.SecurityInterface { + + [TestFixture] + [Preserve (AllMembers = true)] + public class SecKeychainTest { + + [Test] + public void GetTypeID () + { + var typeId = SecKeychain.GetTypeID (); + Assert.That ((int) typeId, Is.GreaterThan (0), "TypeID should be positive"); + } + + [Test] + public void GetDefault () + { + using var keychain = SecKeychain.GetDefault (); + Assert.That (keychain, Is.Not.Null, "Default keychain should exist"); + Assert.That (keychain!.Handle, Is.Not.EqualTo (IntPtr.Zero), "Handle should be non-zero"); + } + + [Test] + public void GetDefault_GetPath () + { + using var keychain = SecKeychain.GetDefault (); + Assert.That (keychain, Is.Not.Null, "Default keychain should exist"); + var path = keychain!.GetPath (); + Assert.That (path, Is.Not.Null, "Path should not be null"); + Assert.That (path, Does.EndWith (".keychain-db").Or.EndWith (".keychain"), "Path should end with .keychain or .keychain-db"); + } + + [Test] + public void Open_InvalidPath_ReturnsNull () + { + using var keychain = SecKeychain.Open ("/nonexistent/path/fake.keychain"); + // SecKeychainOpen may succeed even for nonexistent paths (lazy open) + // so we just verify it doesn't crash + } + + [Test] + public void Open_NullPath_Throws () + { + Assert.Throws (() => SecKeychain.Open (null!)); + } + + [Test] + public void CreateAndDelete_Keychain () + { + var tempPath = Path.Combine (Path.GetTempPath (), $"test-keychain-{Guid.NewGuid ()}.keychain"); + try { + using var defaultKc = SecKeychain.GetDefault (); + Assert.That (defaultKc, Is.Not.Null, "Default keychain should exist"); + + using var opened = SecKeychain.Open (tempPath); + // SecKeychainOpen doesn't create the file; it just prepares a reference + // We can verify the handle is valid + if (opened is not null) { + Assert.That (opened.Handle, Is.Not.EqualTo (IntPtr.Zero)); + } + } finally { + if (File.Exists (tempPath)) + File.Delete (tempPath); + } + } + } +} +#endif // __MACOS__ diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-Security.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-Security.ignore index d93f9bedbf27..a06bbd3250a3 100644 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-Security.ignore +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-Security.ignore @@ -11,8 +11,6 @@ !missing-enum! __CE_DataType not bound !missing-enum! __CE_GeneralNameType not bound !missing-enum! _SecureDownloadTrustCallbackResult not bound -!missing-enum! AuthorizationContextFlags not bound -!missing-enum! AuthorizationResult not bound !missing-enum! CMSCertificateChainMode not bound !missing-enum! CMSSignedAttributes not bound !missing-enum! CMSSignerStatus not bound @@ -492,4 +490,3 @@ !missing-pinvoke! SecCodeValidateFileResource is not bound !missing-field! kSecCFErrorResourceRecursive not bound !missing-field! kSecCodeInfoStapledNotarizationTicket not bound - diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-SecurityInterface.ignore b/tests/xtro-sharpie/api-annotations-dotnet/macOS-SecurityInterface.ignore new file mode 100644 index 000000000000..b97f5e692977 --- /dev/null +++ b/tests/xtro-sharpie/api-annotations-dotnet/macOS-SecurityInterface.ignore @@ -0,0 +1,14 @@ +# Informal protocol selectors bound via [Protocol (IsInformal = true), Model] interfaces +# SFAuthorizationViewDelegate informal protocol +!missing-selector! NSObject::authorizationViewCreatedAuthorization: not bound +!missing-selector! NSObject::authorizationViewDidAuthorize: not bound +!missing-selector! NSObject::authorizationViewDidDeauthorize: not bound +!missing-selector! NSObject::authorizationViewDidHide: not bound +!missing-selector! NSObject::authorizationViewReleasedAuthorization: not bound +!missing-selector! NSObject::authorizationViewShouldDeauthorize: not bound + +# SFCertificatePanelDelegate informal protocol +!missing-selector! NSObject::certificatePanelShowHelp: not bound + +# SFChooseIdentityPanelDelegate informal protocol +!missing-selector! NSObject::chooseIdentityPanelShowHelp: not bound diff --git a/tests/xtro-sharpie/api-annotations-dotnet/macOS-SecurityInterface.todo b/tests/xtro-sharpie/api-annotations-dotnet/macOS-SecurityInterface.todo deleted file mode 100644 index 4a23e01921ad..000000000000 --- a/tests/xtro-sharpie/api-annotations-dotnet/macOS-SecurityInterface.todo +++ /dev/null @@ -1,118 +0,0 @@ -!missing-field! SFAuthorizationPluginViewUserNameKey not bound -!missing-field! SFAuthorizationPluginViewUserShortNameKey not bound -!missing-field! SFCertificateViewDisclosureStateDidChange not bound -!missing-field! SFDisplayViewException not bound -!missing-selector! +SFCertificatePanel::sharedCertificatePanel not bound -!missing-selector! +SFCertificateTrustPanel::sharedCertificateTrustPanel not bound -!missing-selector! +SFChooseIdentityPanel::sharedChooseIdentityPanel not bound -!missing-selector! +SFKeychainSavePanel::sharedKeychainSavePanel not bound -!missing-selector! +SFKeychainSettingsPanel::sharedKeychainSettingsPanel not bound -!missing-selector! NSObject::authorizationViewCreatedAuthorization: not bound -!missing-selector! NSObject::authorizationViewDidAuthorize: not bound -!missing-selector! NSObject::authorizationViewDidDeauthorize: not bound -!missing-selector! NSObject::authorizationViewDidHide: not bound -!missing-selector! NSObject::authorizationViewReleasedAuthorization: not bound -!missing-selector! NSObject::authorizationViewShouldDeauthorize: not bound -!missing-selector! NSObject::certificatePanelShowHelp: not bound -!missing-selector! NSObject::chooseIdentityPanelShowHelp: not bound -!missing-selector! SFAuthorizationPluginView::buttonPressed: not bound -!missing-selector! SFAuthorizationPluginView::callbacks not bound -!missing-selector! SFAuthorizationPluginView::didActivate not bound -!missing-selector! SFAuthorizationPluginView::didDeactivate not bound -!missing-selector! SFAuthorizationPluginView::displayView not bound -!missing-selector! SFAuthorizationPluginView::engineRef not bound -!missing-selector! SFAuthorizationPluginView::firstKeyView not bound -!missing-selector! SFAuthorizationPluginView::firstResponder not bound -!missing-selector! SFAuthorizationPluginView::initWithCallbacks:andEngineRef: not bound -!missing-selector! SFAuthorizationPluginView::lastError not bound -!missing-selector! SFAuthorizationPluginView::lastKeyView not bound -!missing-selector! SFAuthorizationPluginView::setButton:enabled: not bound -!missing-selector! SFAuthorizationPluginView::setEnabled: not bound -!missing-selector! SFAuthorizationPluginView::updateView not bound -!missing-selector! SFAuthorizationPluginView::viewForType: not bound -!missing-selector! SFAuthorizationPluginView::willActivateWithUser: not bound -!missing-selector! SFAuthorizationView::authorization not bound -!missing-selector! SFAuthorizationView::authorizationRights not bound -!missing-selector! SFAuthorizationView::authorizationState not bound -!missing-selector! SFAuthorizationView::authorize: not bound -!missing-selector! SFAuthorizationView::deauthorize: not bound -!missing-selector! SFAuthorizationView::delegate not bound -!missing-selector! SFAuthorizationView::isEnabled not bound -!missing-selector! SFAuthorizationView::setAuthorizationRights: not bound -!missing-selector! SFAuthorizationView::setAutoupdate: not bound -!missing-selector! SFAuthorizationView::setAutoupdate:interval: not bound -!missing-selector! SFAuthorizationView::setDelegate: not bound -!missing-selector! SFAuthorizationView::setEnabled: not bound -!missing-selector! SFAuthorizationView::setFlags: not bound -!missing-selector! SFAuthorizationView::setString: not bound -!missing-selector! SFAuthorizationView::updateStatus: not bound -!missing-selector! SFCertificatePanel::beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:certificates:showGroup: not bound -!missing-selector! SFCertificatePanel::beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:trust:showGroup: not bound -!missing-selector! SFCertificatePanel::certificateView not bound -!missing-selector! SFCertificatePanel::helpAnchor not bound -!missing-selector! SFCertificatePanel::policies not bound -!missing-selector! SFCertificatePanel::runModalForCertificates:showGroup: not bound -!missing-selector! SFCertificatePanel::runModalForTrust:showGroup: not bound -!missing-selector! SFCertificatePanel::setAlternateButtonTitle: not bound -!missing-selector! SFCertificatePanel::setDefaultButtonTitle: not bound -!missing-selector! SFCertificatePanel::setHelpAnchor: not bound -!missing-selector! SFCertificatePanel::setPolicies: not bound -!missing-selector! SFCertificatePanel::setShowsHelp: not bound -!missing-selector! SFCertificatePanel::showsHelp not bound -!missing-selector! SFCertificateTrustPanel::beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:trust:message: not bound -!missing-selector! SFCertificateTrustPanel::informativeText not bound -!missing-selector! SFCertificateTrustPanel::runModalForTrust:message: not bound -!missing-selector! SFCertificateTrustPanel::setInformativeText: not bound -!missing-selector! SFCertificateView::certificate not bound -!missing-selector! SFCertificateView::detailsDisclosed not bound -!missing-selector! SFCertificateView::detailsDisplayed not bound -!missing-selector! SFCertificateView::isEditable not bound -!missing-selector! SFCertificateView::isTrustDisplayed not bound -!missing-selector! SFCertificateView::policies not bound -!missing-selector! SFCertificateView::policiesDisclosed not bound -!missing-selector! SFCertificateView::saveTrustSettings not bound -!missing-selector! SFCertificateView::setCertificate: not bound -!missing-selector! SFCertificateView::setDetailsDisclosed: not bound -!missing-selector! SFCertificateView::setDisplayDetails: not bound -!missing-selector! SFCertificateView::setDisplayTrust: not bound -!missing-selector! SFCertificateView::setEditableTrust: not bound -!missing-selector! SFCertificateView::setPolicies: not bound -!missing-selector! SFCertificateView::setPoliciesDisclosed: not bound -!missing-selector! SFChooseIdentityPanel::beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:identities:message: not bound -!missing-selector! SFChooseIdentityPanel::domain not bound -!missing-selector! SFChooseIdentityPanel::helpAnchor not bound -!missing-selector! SFChooseIdentityPanel::identity not bound -!missing-selector! SFChooseIdentityPanel::informativeText not bound -!missing-selector! SFChooseIdentityPanel::policies not bound -!missing-selector! SFChooseIdentityPanel::runModalForIdentities:message: not bound -!missing-selector! SFChooseIdentityPanel::setAlternateButtonTitle: not bound -!missing-selector! SFChooseIdentityPanel::setDefaultButtonTitle: not bound -!missing-selector! SFChooseIdentityPanel::setDomain: not bound -!missing-selector! SFChooseIdentityPanel::setHelpAnchor: not bound -!missing-selector! SFChooseIdentityPanel::setInformativeText: not bound -!missing-selector! SFChooseIdentityPanel::setPolicies: not bound -!missing-selector! SFChooseIdentityPanel::setShowsHelp: not bound -!missing-selector! SFChooseIdentityPanel::showsHelp not bound -!missing-selector! SFChooseIdentityTableCellView::issuerTextField not bound -!missing-selector! SFChooseIdentityTableCellView::setIssuerTextField: not bound -!missing-selector! SFKeychainSavePanel::beginSheetForDirectory:file:modalForWindow:modalDelegate:didEndSelector:contextInfo: not bound -!missing-selector! SFKeychainSavePanel::error not bound -!missing-selector! SFKeychainSavePanel::keychain not bound -!missing-selector! SFKeychainSavePanel::runModalForDirectory:file: not bound -!missing-selector! SFKeychainSavePanel::setPassword: not bound -!missing-selector! SFKeychainSettingsPanel::beginSheetForWindow:modalDelegate:didEndSelector:contextInfo:settings:keychain: not bound -!missing-selector! SFKeychainSettingsPanel::runModalForSettings:keychain: not bound -!missing-type! SFAuthorizationPluginView not bound -!missing-type! SFAuthorizationView not bound -!missing-type! SFCertificatePanel not bound -!missing-type! SFCertificateTrustPanel not bound -!missing-type! SFCertificateView not bound -!missing-type! SFChooseIdentityPanel not bound -!missing-type! SFChooseIdentityTableCellView not bound -!missing-type! SFKeychainSavePanel not bound -!missing-type! SFKeychainSettingsPanel not bound - -# updated sharpie results -!missing-enum! SFAuthorizationViewState not bound -!missing-enum! SFButtonType not bound -!missing-enum! SFViewType not bound diff --git a/tools/common/Frameworks.cs b/tools/common/Frameworks.cs index 3ae4d13d039f..ec990a781b43 100644 --- a/tools/common/Frameworks.cs +++ b/tools/common/Frameworks.cs @@ -145,6 +145,7 @@ public static Frameworks MacFrameworks { { "MobileCoreServices", "CoreServices", 10, 3 }, { "OpenGL", 10, 3 }, { "SearchKit", "CoreServices", 10,3, "SearchKit" }, + { "SecurityInterface", 10, 3 }, { "SystemConfiguration", 10, 3 }, { "CoreData", 10, 4 },