-
Notifications
You must be signed in to change notification settings - Fork 5k
Add VisualStudioCodeCredential #10979
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
4982a40
VisualStudioCodeCredential prototype
AlexanderSher 67bbb44
- Fix MacosVisualStudioCodeAdapter
AlexanderSher 3224a42
Add VisualStudioCodeCredential
AlexanderSher e19da70
Add correct session recordings
AlexanderSher 2ac4aa2
Fix tests for Mac
AlexanderSher 2ca955c
Address CR comments
AlexanderSher e044d15
Address CR comments
AlexanderSher e5e259e
Address CR comments
AlexanderSher 361f0b3
Address CR comments
AlexanderSher 9880049
- Rename method
AlexanderSher File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
sdk/identity/Azure.Identity/src/IVisualStudioCodeAdapter.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
namespace Azure.Identity | ||
{ | ||
internal interface IVisualStudioCodeAdapter | ||
{ | ||
string GetUserSettingsPath(); | ||
string GetCredentials(string serviceName, string accountName); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace Azure.Identity | ||
{ | ||
internal static class LinuxNativeMethods | ||
{ | ||
public const string SECRET_COLLECTION_SESSION = "session"; | ||
|
||
public enum SecretSchemaAttributeType | ||
{ | ||
SECRET_SCHEMA_ATTRIBUTE_STRING = 0, | ||
SECRET_SCHEMA_ATTRIBUTE_INTEGER = 1, | ||
SECRET_SCHEMA_ATTRIBUTE_BOOLEAN = 2, | ||
} | ||
|
||
public enum SecretSchemaFlags | ||
{ | ||
SECRET_SCHEMA_NONE = 0, | ||
SECRET_SCHEMA_DONT_MATCH_NAME = 2, | ||
} | ||
|
||
internal struct GError | ||
{ | ||
public uint Domain; | ||
public int Code; | ||
public string Message; | ||
} | ||
|
||
public static IntPtr secret_schema_new(string name, SecretSchemaFlags flags, string attribute1, SecretSchemaAttributeType attribute1Type, string attribute2, SecretSchemaAttributeType attribute2Type) | ||
{ | ||
return Imports.secret_schema_new(name, (int)flags, attribute1, (int)attribute1Type, attribute2, (int)attribute2Type, IntPtr.Zero); | ||
} | ||
|
||
public static string secret_password_lookup_sync(IntPtr schemaPtr, IntPtr cancellable, string attribute1Type, string attribute1Value, string attribute2Type, string attribute2Value) | ||
{ | ||
IntPtr passwordPtr = Imports.secret_password_lookup_sync(schemaPtr, cancellable, out IntPtr errorPtr, attribute1Type, attribute1Value, attribute2Type, attribute2Value, IntPtr.Zero); | ||
HandleError(errorPtr, "An error was encountered while reading secret from keyring"); | ||
return passwordPtr != IntPtr.Zero ? Marshal.PtrToStringAnsi(passwordPtr) : null; | ||
} | ||
|
||
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) | ||
{ | ||
_ = Imports.secret_password_store_sync(schemaPtr, collection, label, password, cancellable, out IntPtr errorPtr, attribute1Type, attribute1Value, attribute2Type, attribute2Value, IntPtr.Zero); | ||
HandleError(errorPtr, "An error was encountered while writing secret to keyring"); | ||
} | ||
|
||
public static void secret_password_clear_sync(IntPtr schemaPtr, IntPtr cancellable, string attribute1Type, string attribute1Value, string attribute2Type, string attribute2Value) | ||
schaabs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
_ = Imports.secret_password_clear_sync(schemaPtr, cancellable, out IntPtr errorPtr, attribute1Type, attribute1Value, attribute2Type, attribute2Value, IntPtr.Zero); | ||
HandleError(errorPtr, "An error was encountered while clearing secret from keyring "); | ||
} | ||
|
||
public static void secret_password_free(IntPtr passwordPtr) | ||
{ | ||
if (passwordPtr != IntPtr.Zero) | ||
{ | ||
Imports.secret_password_free(passwordPtr); | ||
} | ||
} | ||
|
||
public static void secret_schema_unref(IntPtr schemaPtr) | ||
{ | ||
if (schemaPtr != IntPtr.Zero) | ||
{ | ||
Imports.secret_schema_unref(schemaPtr); | ||
} | ||
} | ||
|
||
private static void HandleError(IntPtr errorPtr, string errorMessage) | ||
{ | ||
if (errorPtr == IntPtr.Zero) | ||
{ | ||
return; | ||
} | ||
|
||
GError error; | ||
try | ||
{ | ||
error = Marshal.PtrToStructure<GError>(errorPtr); | ||
} | ||
catch (Exception ex) | ||
{ | ||
throw new InvalidOperationException($"An exception was encountered while processing libsecret error: {ex}", ex); | ||
} | ||
|
||
throw new InvalidOperationException($"{errorMessage}, domain:'{error.Domain}', code:'{error.Code}', message:'{error.Message}'"); | ||
} | ||
|
||
private static class Imports | ||
{ | ||
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.System32 | DllImportSearchPath.AssemblyDirectory)] | ||
public static extern IntPtr secret_schema_new(string name, int flags, string attribute1, int attribute1Type, string attribute2, int attribute2Type, IntPtr end); | ||
|
||
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall)] | ||
public static extern void secret_schema_unref (IntPtr schema); | ||
|
||
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] | ||
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); | ||
|
||
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] | ||
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); | ||
|
||
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] | ||
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); | ||
|
||
[DllImport("libsecret-1.so.0", CallingConvention = CallingConvention.StdCall)] | ||
public static extern void secret_password_free(IntPtr password); | ||
} | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
sdk/identity/Azure.Identity/src/LinuxVisualStudioCodeAdapter.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
using Azure.Core; | ||
|
||
namespace Azure.Identity | ||
{ | ||
internal sealed class LinuxVisualStudioCodeAdapter : IVisualStudioCodeAdapter | ||
{ | ||
private static readonly string s_userSettingsJsonPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Code", "User", "settings.json"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seem like settings on Linux is under $HOME/.config/Code/User/settings.json. See https://vscode.readthedocs.io/en/latest/getstarted/settings/ |
||
|
||
public string GetUserSettingsPath() => s_userSettingsJsonPath; | ||
|
||
public string GetCredentials(string serviceName, string accountName) | ||
{ | ||
Argument.AssertNotNullOrEmpty(serviceName, nameof(serviceName)); | ||
Argument.AssertNotNullOrEmpty(accountName, nameof(accountName)); | ||
|
||
IntPtr schemaPtr = GetLibsecretSchema(); | ||
|
||
try | ||
{ | ||
return LookupPassword(schemaPtr, serviceName, accountName); | ||
} | ||
finally | ||
{ | ||
LinuxNativeMethods.secret_schema_unref(schemaPtr); | ||
} | ||
} | ||
|
||
private static string LookupPassword(in IntPtr schemaPtr, string serviceName, string accountName) | ||
=> LinuxNativeMethods.secret_password_lookup_sync(schemaPtr, IntPtr.Zero, "service", serviceName, "account", accountName); | ||
|
||
private static IntPtr GetLibsecretSchema() | ||
=> LinuxNativeMethods.secret_schema_new("org.freedesktop.Secret.Generic", | ||
LinuxNativeMethods.SecretSchemaFlags.SECRET_SCHEMA_DONT_MATCH_NAME, | ||
"service", | ||
LinuxNativeMethods.SecretSchemaAttributeType.SECRET_SCHEMA_ATTRIBUTE_STRING, | ||
"account", | ||
LinuxNativeMethods.SecretSchemaAttributeType.SECRET_SCHEMA_ATTRIBUTE_STRING); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
|
||
namespace Azure.Identity | ||
{ | ||
internal static class MacosNativeMethods | ||
{ | ||
public const int SecStatusCodeSuccess = 0; | ||
public const int SecStatusCodeNoSuchKeychain = -25294; | ||
public const int SecStatusCodeInvalidKeychain = -25295; | ||
public const int SecStatusCodeAuthFailed = -25293; | ||
public const int SecStatusCodeDuplicateItem = -25299; | ||
public const int SecStatusCodeItemNotFound = -25300; | ||
public const int SecStatusCodeInteractionNotAllowed = -25308; | ||
public const int SecStatusCodeInteractionRequired = -25315; | ||
public const int SecStatusCodeNoSuchAttr = -25303; | ||
|
||
public readonly struct CFRange | ||
{ | ||
public readonly int Location, Length; | ||
public CFRange (int location, int length) | ||
{ | ||
Location = location; | ||
Length = length; | ||
} | ||
} | ||
|
||
public static void SecKeychainFindGenericPassword(IntPtr keychainOrArray, string serviceName, string accountName, out int passwordLength, out IntPtr credentialsPtr, out IntPtr itemRef) | ||
{ | ||
byte[] serviceNameBytes = Encoding.UTF8.GetBytes(serviceName); | ||
byte[] accountNameBytes = Encoding.UTF8.GetBytes(accountName); | ||
|
||
ThrowIfError(Imports.SecKeychainFindGenericPassword(keychainOrArray, serviceNameBytes.Length, serviceNameBytes, accountNameBytes.Length, accountNameBytes, out passwordLength, out credentialsPtr, out itemRef)); | ||
} | ||
|
||
public static void SecKeychainAddGenericPassword(IntPtr keychainOrArray, string serviceName, string accountName, string password, out IntPtr itemRef) | ||
{ | ||
byte[] serviceNameBytes = Encoding.UTF8.GetBytes(serviceName); | ||
byte[] accountNameBytes = Encoding.UTF8.GetBytes(accountName); | ||
byte[] passwordBytes = Encoding.UTF8.GetBytes(password); | ||
|
||
ThrowIfError(Imports.SecKeychainAddGenericPassword(keychainOrArray, serviceNameBytes.Length, serviceNameBytes, accountNameBytes.Length, accountNameBytes, password.Length, passwordBytes, out itemRef)); | ||
} | ||
|
||
public static void SecKeychainItemDelete(IntPtr itemRef) => ThrowIfError(Imports.SecKeychainItemDelete(itemRef)); | ||
|
||
public static void SecKeychainItemFreeContent(IntPtr attrList, IntPtr data) => ThrowIfError(Imports.SecKeychainItemFreeContent(attrList, data)); | ||
|
||
public static void CFRelease(IntPtr cfRef) | ||
{ | ||
if (cfRef != IntPtr.Zero) | ||
{ | ||
Imports.CFRelease(cfRef); | ||
} | ||
} | ||
|
||
private static void ThrowIfError(int status) | ||
{ | ||
if (status != SecStatusCodeSuccess) | ||
{ | ||
throw new InvalidOperationException(GetErrorMessageString(status)); | ||
} | ||
} | ||
|
||
private static string GetErrorMessageString(int status) | ||
{ | ||
IntPtr messagePtr = IntPtr.Zero; | ||
try | ||
{ | ||
messagePtr = Imports.SecCopyErrorMessageString(status, IntPtr.Zero); | ||
return GetStringFromCFStringPtr(messagePtr); | ||
} | ||
catch | ||
{ | ||
return status switch | ||
{ | ||
SecStatusCodeNoSuchKeychain => $"The keychain does not exist. [0x{status:x}]", | ||
SecStatusCodeInvalidKeychain => $"The keychain is not valid. [0x{status:x}]", | ||
SecStatusCodeAuthFailed => $"Authorization/Authentication failed. [0x{status:x}]", | ||
SecStatusCodeDuplicateItem => $"The item already exists. [0x{status:x}]", | ||
SecStatusCodeItemNotFound => $"The item cannot be found. [0x{status:x}]", | ||
SecStatusCodeInteractionNotAllowed => $"Interaction with the Security Server is not allowed. [0x{status:x}]", | ||
SecStatusCodeInteractionRequired => $"User interaction is required. [0x{status:x}]", | ||
SecStatusCodeNoSuchAttr => $"The attribute does not exist. [0x{status:x}]", | ||
_ => $"Unknown error. [0x{status:x}]", | ||
}; | ||
} | ||
finally | ||
{ | ||
CFRelease(messagePtr); | ||
} | ||
} | ||
|
||
private static string GetStringFromCFStringPtr(IntPtr handle) | ||
{ | ||
IntPtr stringPtr = IntPtr.Zero; | ||
try | ||
{ | ||
int length = Imports.CFStringGetLength (handle); | ||
stringPtr = Imports.CFStringGetCharactersPtr(handle); | ||
|
||
if (stringPtr == IntPtr.Zero) | ||
{ | ||
var range = new CFRange(0, length); | ||
stringPtr = Marshal.AllocCoTaskMem(length * 2); | ||
Imports.CFStringGetCharacters(handle, range, stringPtr); | ||
} | ||
|
||
return Marshal.PtrToStringAuto(stringPtr, length); | ||
} | ||
finally | ||
{ | ||
if (stringPtr != IntPtr.Zero) | ||
{ | ||
Marshal.FreeCoTaskMem (stringPtr); | ||
} | ||
} | ||
} | ||
|
||
public static class Imports | ||
{ | ||
private const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; | ||
private const string SecurityLibrary = "/System/Library/Frameworks/Security.framework/Security"; | ||
|
||
[DllImport (CoreFoundationLibrary, CharSet=CharSet.Unicode)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)] | ||
public static extern int CFStringGetLength (IntPtr handle); | ||
|
||
[DllImport (CoreFoundationLibrary, CharSet=CharSet.Unicode)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)] | ||
public static extern IntPtr CFStringGetCharactersPtr (IntPtr handle); | ||
|
||
[DllImport (CoreFoundationLibrary, CharSet=CharSet.Unicode)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)] | ||
public static extern IntPtr CFStringGetCharacters (IntPtr handle, CFRange range, IntPtr buffer); | ||
|
||
[DllImport (CoreFoundationLibrary, CharSet=CharSet.Unicode)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)] | ||
public static extern void CFRelease(IntPtr cfRef); | ||
|
||
[DllImport (SecurityLibrary)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)] | ||
public static extern int SecKeychainFindGenericPassword ( | ||
IntPtr keychainOrArray, | ||
int serviceNameLength, | ||
byte[] serviceName, | ||
int accountNameLength, | ||
byte[] accountName, | ||
out int passwordLength, | ||
out IntPtr passwordData, | ||
out IntPtr itemRef); | ||
|
||
[DllImport (SecurityLibrary)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)] | ||
public static extern int SecKeychainAddGenericPassword ( | ||
IntPtr keychain, | ||
int serviceNameLength, | ||
byte[] serviceName, | ||
int accountNameLength, | ||
byte[] accountName, | ||
int passwordLength, | ||
byte[] passwordData, | ||
out IntPtr itemRef); | ||
|
||
[DllImport (SecurityLibrary)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)] | ||
public static extern int SecKeychainItemDelete(IntPtr itemRef); | ||
|
||
[DllImport (SecurityLibrary)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)] | ||
public static extern int SecKeychainItemFreeContent (IntPtr attrList, IntPtr data); | ||
|
||
[DllImport (SecurityLibrary)] | ||
[DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.SafeDirectories)] | ||
public static extern IntPtr SecCopyErrorMessageString (int status, IntPtr reserved); | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.