Skip to content

Commit 048c413

Browse files
Add VisualStudioCodeCredential (#10979)
* VisualStudioCodeCredential prototype * - Fix MacosVisualStudioCodeAdapter - Remove unused code from VisualStudioCodeCredential * Add VisualStudioCodeCredential * Add correct session recordings * Fix tests for Mac * Address CR comments * Address CR comments * Address CR comments * Address CR comments * - Rename method - On Linux, disable tests that require Libsecret
1 parent c67e2c3 commit 048c413

32 files changed

+4170
-7
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.Identity
5+
{
6+
internal interface IVisualStudioCodeAdapter
7+
{
8+
string GetUserSettingsPath();
9+
string GetCredentials(string serviceName, string accountName);
10+
}
11+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
namespace Azure.Identity
8+
{
9+
internal static class LinuxNativeMethods
10+
{
11+
public const string SECRET_COLLECTION_SESSION = "session";
12+
13+
public enum SecretSchemaAttributeType
14+
{
15+
SECRET_SCHEMA_ATTRIBUTE_STRING = 0,
16+
SECRET_SCHEMA_ATTRIBUTE_INTEGER = 1,
17+
SECRET_SCHEMA_ATTRIBUTE_BOOLEAN = 2,
18+
}
19+
20+
public enum SecretSchemaFlags
21+
{
22+
SECRET_SCHEMA_NONE = 0,
23+
SECRET_SCHEMA_DONT_MATCH_NAME = 2,
24+
}
25+
26+
internal struct GError
27+
{
28+
public uint Domain;
29+
public int Code;
30+
public string Message;
31+
}
32+
33+
public static IntPtr secret_schema_new(string name, SecretSchemaFlags flags, string attribute1, SecretSchemaAttributeType attribute1Type, string attribute2, SecretSchemaAttributeType attribute2Type)
34+
{
35+
return Imports.secret_schema_new(name, (int)flags, attribute1, (int)attribute1Type, attribute2, (int)attribute2Type, IntPtr.Zero);
36+
}
37+
38+
public static string secret_password_lookup_sync(IntPtr schemaPtr, IntPtr cancellable, string attribute1Type, string attribute1Value, string attribute2Type, string attribute2Value)
39+
{
40+
IntPtr passwordPtr = Imports.secret_password_lookup_sync(schemaPtr, cancellable, out IntPtr errorPtr, attribute1Type, attribute1Value, attribute2Type, attribute2Value, IntPtr.Zero);
41+
HandleError(errorPtr, "An error was encountered while reading secret from keyring");
42+
return passwordPtr != IntPtr.Zero ? Marshal.PtrToStringAnsi(passwordPtr) : null;
43+
}
44+
45+
public static void secret_password_store_sync(IntPtr schemaPtr, string collection, string label, string password, IntPtr cancellable, string attribute1Type, string attribute1Value, string attribute2Type, string attribute2Value)
46+
{
47+
_ = Imports.secret_password_store_sync(schemaPtr, collection, label, password, cancellable, out IntPtr errorPtr, attribute1Type, attribute1Value, attribute2Type, attribute2Value, IntPtr.Zero);
48+
HandleError(errorPtr, "An error was encountered while writing secret to keyring");
49+
}
50+
51+
public static void secret_password_clear_sync(IntPtr schemaPtr, IntPtr cancellable, string attribute1Type, string attribute1Value, string attribute2Type, string attribute2Value)
52+
{
53+
_ = Imports.secret_password_clear_sync(schemaPtr, cancellable, out IntPtr errorPtr, attribute1Type, attribute1Value, attribute2Type, attribute2Value, IntPtr.Zero);
54+
HandleError(errorPtr, "An error was encountered while clearing secret from keyring ");
55+
}
56+
57+
public static void secret_password_free(IntPtr passwordPtr)
58+
{
59+
if (passwordPtr != IntPtr.Zero)
60+
{
61+
Imports.secret_password_free(passwordPtr);
62+
}
63+
}
64+
65+
public static void secret_schema_unref(IntPtr schemaPtr)
66+
{
67+
if (schemaPtr != IntPtr.Zero)
68+
{
69+
Imports.secret_schema_unref(schemaPtr);
70+
}
71+
}
72+
73+
private static void HandleError(IntPtr errorPtr, string errorMessage)
74+
{
75+
if (errorPtr == IntPtr.Zero)
76+
{
77+
return;
78+
}
79+
80+
GError error;
81+
try
82+
{
83+
error = Marshal.PtrToStructure<GError>(errorPtr);
84+
}
85+
catch (Exception ex)
86+
{
87+
throw new InvalidOperationException($"An exception was encountered while processing libsecret error: {ex}", ex);
88+
}
89+
90+
throw new InvalidOperationException($"{errorMessage}, domain:'{error.Domain}', code:'{error.Code}', message:'{error.Message}'");
91+
}
92+
93+
private static class Imports
94+
{
95+
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
96+
[DefaultDllImportSearchPaths(DllImportSearchPath.System32 | DllImportSearchPath.AssemblyDirectory)]
97+
public static extern IntPtr secret_schema_new(string name, int flags, string attribute1, int attribute1Type, string attribute2, int attribute2Type, IntPtr end);
98+
99+
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall)]
100+
public static extern void secret_schema_unref (IntPtr schema);
101+
102+
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
103+
public static extern IntPtr secret_password_lookup_sync(IntPtr schema, IntPtr cancellable, out IntPtr error, string attribute1Type, string attribute1Value, string attribute2Type, string attribute2Value, IntPtr end);
104+
105+
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
106+
public static extern int secret_password_store_sync(IntPtr schema, string collection, string label, string password, IntPtr cancellable, out IntPtr error, string attribute1Type, string attribute1Value, string attribute2Type, string attribute2Value, IntPtr end);
107+
108+
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
109+
public static extern int secret_password_clear_sync(IntPtr schema, IntPtr cancellable, out IntPtr error, string attribute1Type, string attribute1Value, string attribute2Type, string attribute2Value, IntPtr end);
110+
111+
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall)]
112+
public static extern void secret_password_free(IntPtr password);
113+
}
114+
}
115+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.IO;
6+
using System.Runtime.InteropServices;
7+
using Azure.Core;
8+
9+
namespace Azure.Identity
10+
{
11+
internal sealed class LinuxVisualStudioCodeAdapter : IVisualStudioCodeAdapter
12+
{
13+
private static readonly string s_userSettingsJsonPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Code", "User", "settings.json");
14+
15+
public string GetUserSettingsPath() => s_userSettingsJsonPath;
16+
17+
public string GetCredentials(string serviceName, string accountName)
18+
{
19+
Argument.AssertNotNullOrEmpty(serviceName, nameof(serviceName));
20+
Argument.AssertNotNullOrEmpty(accountName, nameof(accountName));
21+
22+
IntPtr schemaPtr = GetLibsecretSchema();
23+
24+
try
25+
{
26+
return LookupPassword(schemaPtr, serviceName, accountName);
27+
}
28+
finally
29+
{
30+
LinuxNativeMethods.secret_schema_unref(schemaPtr);
31+
}
32+
}
33+
34+
private static string LookupPassword(in IntPtr schemaPtr, string serviceName, string accountName)
35+
=> LinuxNativeMethods.secret_password_lookup_sync(schemaPtr, IntPtr.Zero, "service", serviceName, "account", accountName);
36+
37+
private static IntPtr GetLibsecretSchema()
38+
=> LinuxNativeMethods.secret_schema_new("org.freedesktop.Secret.Generic",
39+
LinuxNativeMethods.SecretSchemaFlags.SECRET_SCHEMA_DONT_MATCH_NAME,
40+
"service",
41+
LinuxNativeMethods.SecretSchemaAttributeType.SECRET_SCHEMA_ATTRIBUTE_STRING,
42+
"account",
43+
LinuxNativeMethods.SecretSchemaAttributeType.SECRET_SCHEMA_ATTRIBUTE_STRING);
44+
}
45+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
using System.Text;
7+
8+
namespace Azure.Identity
9+
{
10+
internal static class MacosNativeMethods
11+
{
12+
public const int SecStatusCodeSuccess = 0;
13+
public const int SecStatusCodeNoSuchKeychain = -25294;
14+
public const int SecStatusCodeInvalidKeychain = -25295;
15+
public const int SecStatusCodeAuthFailed = -25293;
16+
public const int SecStatusCodeDuplicateItem = -25299;
17+
public const int SecStatusCodeItemNotFound = -25300;
18+
public const int SecStatusCodeInteractionNotAllowed = -25308;
19+
public const int SecStatusCodeInteractionRequired = -25315;
20+
public const int SecStatusCodeNoSuchAttr = -25303;
21+
22+
public readonly struct CFRange
23+
{
24+
public readonly int Location, Length;
25+
public CFRange (int location, int length)
26+
{
27+
Location = location;
28+
Length = length;
29+
}
30+
}
31+
32+
public static void SecKeychainFindGenericPassword(IntPtr keychainOrArray, string serviceName, string accountName, out int passwordLength, out IntPtr credentialsPtr, out IntPtr itemRef)
33+
{
34+
byte[] serviceNameBytes = Encoding.UTF8.GetBytes(serviceName);
35+
byte[] accountNameBytes = Encoding.UTF8.GetBytes(accountName);
36+
37+
ThrowIfError(Imports.SecKeychainFindGenericPassword(keychainOrArray, serviceNameBytes.Length, serviceNameBytes, accountNameBytes.Length, accountNameBytes, out passwordLength, out credentialsPtr, out itemRef));
38+
}
39+
40+
public static void SecKeychainAddGenericPassword(IntPtr keychainOrArray, string serviceName, string accountName, string password, out IntPtr itemRef)
41+
{
42+
byte[] serviceNameBytes = Encoding.UTF8.GetBytes(serviceName);
43+
byte[] accountNameBytes = Encoding.UTF8.GetBytes(accountName);
44+
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
45+
46+
ThrowIfError(Imports.SecKeychainAddGenericPassword(keychainOrArray, serviceNameBytes.Length, serviceNameBytes, accountNameBytes.Length, accountNameBytes, password.Length, passwordBytes, out itemRef));
47+
}
48+
49+
public static void SecKeychainItemDelete(IntPtr itemRef) => ThrowIfError(Imports.SecKeychainItemDelete(itemRef));
50+
51+
public static void SecKeychainItemFreeContent(IntPtr attrList, IntPtr data) => ThrowIfError(Imports.SecKeychainItemFreeContent(attrList, data));
52+
53+
public static void CFRelease(IntPtr cfRef)
54+
{
55+
if (cfRef != IntPtr.Zero)
56+
{
57+
Imports.CFRelease(cfRef);
58+
}
59+
}
60+
61+
private static void ThrowIfError(int status)
62+
{
63+
if (status != SecStatusCodeSuccess)
64+
{
65+
throw new InvalidOperationException(GetErrorMessageString(status));
66+
}
67+
}
68+
69+
private static string GetErrorMessageString(int status)
70+
{
71+
IntPtr messagePtr = IntPtr.Zero;
72+
try
73+
{
74+
messagePtr = Imports.SecCopyErrorMessageString(status, IntPtr.Zero);
75+
return GetStringFromCFStringPtr(messagePtr);
76+
}
77+
catch
78+
{
79+
return status switch
80+
{
81+
SecStatusCodeNoSuchKeychain => $"The keychain does not exist. [0x{status:x}]",
82+
SecStatusCodeInvalidKeychain => $"The keychain is not valid. [0x{status:x}]",
83+
SecStatusCodeAuthFailed => $"Authorization/Authentication failed. [0x{status:x}]",
84+
SecStatusCodeDuplicateItem => $"The item already exists. [0x{status:x}]",
85+
SecStatusCodeItemNotFound => $"The item cannot be found. [0x{status:x}]",
86+
SecStatusCodeInteractionNotAllowed => $"Interaction with the Security Server is not allowed. [0x{status:x}]",
87+
SecStatusCodeInteractionRequired => $"User interaction is required. [0x{status:x}]",
88+
SecStatusCodeNoSuchAttr => $"The attribute does not exist. [0x{status:x}]",
89+
_ => $"Unknown error. [0x{status:x}]",
90+
};
91+
}
92+
finally
93+
{
94+
CFRelease(messagePtr);
95+
}
96+
}
97+
98+
private static string GetStringFromCFStringPtr(IntPtr handle)
99+
{
100+
IntPtr stringPtr = IntPtr.Zero;
101+
try
102+
{
103+
int length = Imports.CFStringGetLength (handle);
104+
stringPtr = Imports.CFStringGetCharactersPtr(handle);
105+
106+
if (stringPtr == IntPtr.Zero)
107+
{
108+
var range = new CFRange(0, length);
109+
stringPtr = Marshal.AllocCoTaskMem(length * 2);
110+
Imports.CFStringGetCharacters(handle, range, stringPtr);
111+
}
112+
113+
return Marshal.PtrToStringAuto(stringPtr, length);
114+
}
115+
finally
116+
{
117+
if (stringPtr != IntPtr.Zero)
118+
{
119+
Marshal.FreeCoTaskMem (stringPtr);
120+
}
121+
}
122+
}
123+
124+
public static class Imports
125+
{
126+
private const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
127+
private const string SecurityLibrary = "/System/Library/Frameworks/Security.framework/Security";
128+
129+
[DllImport (CoreFoundationLibrary, CharSet=CharSet.Unicode)]
130+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
131+
public static extern int CFStringGetLength (IntPtr handle);
132+
133+
[DllImport (CoreFoundationLibrary, CharSet=CharSet.Unicode)]
134+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
135+
public static extern IntPtr CFStringGetCharactersPtr (IntPtr handle);
136+
137+
[DllImport (CoreFoundationLibrary, CharSet=CharSet.Unicode)]
138+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
139+
public static extern IntPtr CFStringGetCharacters (IntPtr handle, CFRange range, IntPtr buffer);
140+
141+
[DllImport (CoreFoundationLibrary, CharSet=CharSet.Unicode)]
142+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
143+
public static extern void CFRelease(IntPtr cfRef);
144+
145+
[DllImport (SecurityLibrary)]
146+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
147+
public static extern int SecKeychainFindGenericPassword (
148+
IntPtr keychainOrArray,
149+
int serviceNameLength,
150+
byte[] serviceName,
151+
int accountNameLength,
152+
byte[] accountName,
153+
out int passwordLength,
154+
out IntPtr passwordData,
155+
out IntPtr itemRef);
156+
157+
[DllImport (SecurityLibrary)]
158+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
159+
public static extern int SecKeychainAddGenericPassword (
160+
IntPtr keychain,
161+
int serviceNameLength,
162+
byte[] serviceName,
163+
int accountNameLength,
164+
byte[] accountName,
165+
int passwordLength,
166+
byte[] passwordData,
167+
out IntPtr itemRef);
168+
169+
[DllImport (SecurityLibrary)]
170+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
171+
public static extern int SecKeychainItemDelete(IntPtr itemRef);
172+
173+
[DllImport (SecurityLibrary)]
174+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
175+
public static extern int SecKeychainItemFreeContent (IntPtr attrList, IntPtr data);
176+
177+
[DllImport (SecurityLibrary)]
178+
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)]
179+
public static extern IntPtr SecCopyErrorMessageString (int status, IntPtr reserved);
180+
}
181+
}
182+
}

0 commit comments

Comments
 (0)