Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ indent_size = 2

# Dotnet code style settings:
[*.{cs,vb}]
tab_width = 4

# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary
Expand Down Expand Up @@ -57,6 +59,9 @@ dotnet_style_require_accessibility_modifiers = omit_if_default:error
# IDE0040: Add accessibility modifiers
dotnet_diagnostic.IDE0040.severity = error

# IDE1100: Error reading content of source file 'Project.TargetFrameworkMoniker' (i.e. from ThisAssembly)
dotnet_diagnostic.IDE1100.severity = none

[*.cs]
# Top-level files are definitely OK
csharp_using_directive_placement = outside_namespace:silent
Expand Down
28 changes: 24 additions & 4 deletions .github/workflows/includes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ on:
branches:
- 'main'
paths:
- '**.md'
- '**.md'
- '!changelog.md'
- 'osmfeula.txt'

jobs:
includes:
Expand All @@ -31,14 +32,33 @@ jobs:
- name: +Mᐁ includes
uses: devlooped/actions-includes@v1

- name: 📝 OSMF EULA
shell: pwsh
run: |
$file = "osmfeula.txt"
$props = "src/Directory.Build.props"
if (-not (test-path $file) -or -not (test-path $props)) {
exit 0
}

$product = dotnet msbuild $props -getproperty:Product
if (-not $product) {
write-error 'To use OSMF EULA, ensure the $(Product) property is set in Directory.props'
exit 1
}

((get-content -raw $file) -replace '\$product\$',$product).trim() | set-content $file

- name: ✍ pull request
uses: peter-evans/create-pull-request@v6
uses: peter-evans/create-pull-request@v8
with:
add-paths: '**.md'
add-paths: |
**.md
*.txt
base: main
branch: markdown-includes
delete-branch: true
labels: docs
labels: dependencies
author: ${{ env.BOT_AUTHOR }}
committer: ${{ env.BOT_AUTHOR }}
commit-message: +Mᐁ includes
Expand Down
298 changes: 158 additions & 140 deletions .netconfig

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Core/CommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ public CommandContext()
gitPath,
FileSystem.GetCurrentDirectory()
);
Settings = new Settings(Environment, Git);
Settings = new MacOSSettings(Environment, Git, Trace);
}
else if (PlatformUtils.IsLinux())
{
Expand Down
1 change: 1 addition & 0 deletions src/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static class Constants

public const string GcmDataDirectoryName = ".gcm";

public const string MacOSBundleId = "git-credential-manager";
public static readonly Guid DevBoxPartnerId = new("e3171dd9-9a5f-e5be-b36c-cc7c4f3f3bcf");

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Interop/Linux/SecretServiceCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private unsafe IEnumerable<ICredential> Enumerate(string service, string account
secService,
ref schema,
queryAttrs,
SecretSearchFlags.SECRET_SEARCH_UNLOCK,
SecretSearchFlags.SECRET_SEARCH_UNLOCK | SecretSearchFlags.SECRET_SEARCH_ALL,
IntPtr.Zero,
out error);

Expand Down
34 changes: 8 additions & 26 deletions src/Core/Interop/MacOS/MacOSKeychain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ public IList<string> GetAccounts(string service)
if (typeId == CFArrayGetTypeID())
{
int len = (int)CFArrayGetCount(resultPtr);
// NOTE: removed len from HashSet ctor since it's not supported in NS2.0
var accounts = new HashSet<string>();
for (int i = 0; i < len; i++)
{
Expand Down Expand Up @@ -303,35 +302,18 @@ private static string GetStringAttribute(IntPtr dict, IntPtr key)
return null;
}

IntPtr buffer = IntPtr.Zero;
try
if (CFDictionaryGetValueIfPresent(dict, key, out IntPtr value) && value != IntPtr.Zero)
{
if (CFDictionaryGetValueIfPresent(dict, key, out IntPtr value) && value != IntPtr.Zero)
if (CFGetTypeID(value) == CFStringGetTypeID())
{
if (CFGetTypeID(value) == CFStringGetTypeID())
{
int stringLength = (int)CFStringGetLength(value);
int bufferSize = stringLength + 1;
buffer = Marshal.AllocHGlobal(bufferSize);
if (CFStringGetCString(value, buffer, bufferSize, CFStringEncoding.kCFStringEncodingUTF8))
{
return Marshal.PtrToStringAuto(buffer, stringLength);
}
}

if (CFGetTypeID(value) == CFDataGetTypeID())
{
int length = CFDataGetLength(value);
IntPtr ptr = CFDataGetBytePtr(value);
return Marshal.PtrToStringAuto(ptr, length);
}
return CFStringToString(value);
}
}
finally
{
if (buffer != IntPtr.Zero)

if (CFGetTypeID(value) == CFDataGetTypeID())
{
Marshal.FreeHGlobal(buffer);
int length = CFDataGetLength(value);
IntPtr ptr = CFDataGetBytePtr(value);
return Marshal.PtrToStringAuto(ptr, length);
}
}

Expand Down
96 changes: 96 additions & 0 deletions src/Core/Interop/MacOS/MacOSPreferences.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using GitCredentialManager.Interop.MacOS.Native;
using static GitCredentialManager.Interop.MacOS.Native.CoreFoundation;

namespace GitCredentialManager.Interop.MacOS;

public class MacOSPreferences
{
private readonly string _appId;

public MacOSPreferences(string appId)
{
EnsureArgument.NotNull(appId, nameof(appId));

_appId = appId;
}

/// <summary>
/// Return a <see cref="string"/> typed value from the app preferences.
/// </summary>
/// <param name="key">Preference name.</param>
/// <exception cref="InvalidOperationException">Thrown if the preference is not a string.</exception>
/// <returns>
/// <see cref="string"/> or null if the preference with the given key does not exist.
/// </returns>
public string GetString(string key)
{
return TryGet(key, CFStringToString, out string value)
? value
: null;
}

/// <summary>
/// Return a <see cref="int"/> typed value from the app preferences.
/// </summary>
/// <param name="key">Preference name.</param>
/// <exception cref="InvalidOperationException">Thrown if the preference is not an integer.</exception>
/// <returns>
/// <see cref="int"/> or null if the preference with the given key does not exist.
/// </returns>
public int? GetInteger(string key)
{
return TryGet(key, CFNumberToInt32, out int value)
? value
: null;
}

/// <summary>
/// Return a <see cref="IDictionary{TKey,TValue}"/> typed value from the app preferences.
/// </summary>
/// <param name="key">Preference name.</param>
/// <exception cref="InvalidOperationException">Thrown if the preference is not a dictionary.</exception>
/// <returns>
/// <see cref="IDictionary{TKey,TValue}"/> or null if the preference with the given key does not exist.
/// </returns>
public IDictionary<string, string> GetDictionary(string key)
{
return TryGet(key, CFDictionaryToDictionary, out IDictionary<string, string> value)
? value
: null;
}

private bool TryGet<T>(string key, Func<IntPtr, T> converter, out T value)
{
IntPtr cfValue = IntPtr.Zero;
IntPtr keyPtr = IntPtr.Zero;
IntPtr appIdPtr = CreateAppIdPtr();

try
{
keyPtr = CFStringCreateWithCString(IntPtr.Zero, key, CFStringEncoding.kCFStringEncodingUTF8);
cfValue = CFPreferencesCopyAppValue(keyPtr, appIdPtr);

if (cfValue == IntPtr.Zero)
{
value = default;
return false;
}

value = converter(cfValue);
return true;
}
finally
{
if (cfValue != IntPtr.Zero) CFRelease(cfValue);
if (keyPtr != IntPtr.Zero) CFRelease(keyPtr);
if (appIdPtr != IntPtr.Zero) CFRelease(appIdPtr);
}
}

private IntPtr CreateAppIdPtr()
{
return CFStringCreateWithCString(IntPtr.Zero, _appId, CFStringEncoding.kCFStringEncodingUTF8);
}
}
67 changes: 67 additions & 0 deletions src/Core/Interop/MacOS/MacOSSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;

namespace GitCredentialManager.Interop.MacOS
{
/// <summary>
/// Reads settings from Git configuration, environment variables, and defaults from the system.
/// </summary>
public class MacOSSettings : Settings
{
private readonly ITrace _trace;

public MacOSSettings(IEnvironment environment, IGit git, ITrace trace)
: base(environment, git)
{
EnsureArgument.NotNull(trace, nameof(trace));
_trace = trace;

PlatformUtils.EnsureMacOS();
}

protected override bool TryGetExternalDefault(string section, string scope, string property, out string value)
{
value = null;

try
{
// Check for app default preferences for our bundle ID.
// Defaults can be deployed system administrators via device management profiles.
var prefs = new MacOSPreferences(Constants.MacOSBundleId);
IDictionary<string, string> dict = prefs.GetDictionary("configuration");

if (dict is null)
{
// No configuration key exists
return false;
}

// Wrap the raw dictionary in one configured with the Git configuration key comparer.
// This means we can use the same key comparison rules as Git in our configuration plist dict,
// That is, sections and names are insensitive to case, but the scope is case-sensitive.
var config = new Dictionary<string, string>(dict, GitConfigurationKeyComparer.Instance);

string name = string.IsNullOrWhiteSpace(scope)
? $"{section}.{property}"
: $"{section}.{scope}.{property}";

if (!config.TryGetValue(name, out value))
{
// No property exists
return false;
}

_trace.WriteLine($"Default setting found in app preferences: {name}={value}");
return true;
}
catch (Exception ex)
{
// Reading defaults is not critical to the operation of the application
// so we can ignore any errors and just log the failure.
_trace.WriteLine("Failed to read default setting from app preferences.");
_trace.WriteException(ex);
return false;
}
}
}
}
Loading