Skip to content
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

Add VisualStudioCodeCredential #10979

Merged
merged 10 commits into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions sdk/identity/Azure.Identity/src/IVisualStudioCodeAdapter.cs
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);
}
}
116 changes: 116 additions & 0 deletions sdk/identity/Azure.Identity/src/LinuxNativeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// 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_DEFAULT = "default";
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 var 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 var 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)
{
_ = Imports.secret_password_clear_sync(schemaPtr, cancellable, out var 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}");
}

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 sdk/identity/Azure.Identity/src/LinuxVisualStudioCodeAdapter.cs
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");
Copy link
Member

Choose a reason for hiding this comment

The 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);
}
}
191 changes: 191 additions & 0 deletions sdk/identity/Azure.Identity/src/MacosNativeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// 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
{
switch (status)
{
case SecStatusCodeNoSuchKeychain:
return $"The keychain does not exist. [0x{status:x}]";
case SecStatusCodeInvalidKeychain:
return $"The keychain is not valid. [0x{status:x}]";
case SecStatusCodeAuthFailed:
return $"Authorization/Authentication failed. [0x{status:x}]";
case SecStatusCodeDuplicateItem:
return $"The item already exists. [0x{status:x}]";
case SecStatusCodeItemNotFound:
return $"The item cannot be found. [0x{status:x}]";
case SecStatusCodeInteractionNotAllowed:
return $"Interaction with the Security Server is not allowed. [0x{status:x}]";
case SecStatusCodeInteractionRequired:
return $"User interaction is required. [0x{status:x}]";
case SecStatusCodeNoSuchAttr:
return $"The attribute does not exist. [0x{status:x}]";
default:
return $"Unknown error. [0x{status:x}]";
}
}
finally
{
CFRelease(messagePtr);
}
}

private static string GetStringFromCFStringPtr(IntPtr handle)
{
IntPtr stringPtr = IntPtr.Zero;
try
{
stringPtr = Imports.CFStringGetCharactersPtr(handle);

if (stringPtr == IntPtr.Zero)
{
int length = Imports.CFStringGetLength (handle);
var range = new MacosNativeMethods.CFRange(0, length);
stringPtr = Marshal.AllocCoTaskMem(length * 2);
Imports.CFStringGetCharacters(handle, range, stringPtr);
}

return Marshal.PtrToStringAuto(stringPtr, 0);
}
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);
}
}
}
Loading