LibGit2Sharp:如何使用自定义 HTTP 身份验证中的个人访问令牌将本地存储库提交推送到 Azure DevOps 远程存储库 header?

LibGit2Sharp: How to push a local repo commit to Azure DevOps remote repo using a Personal Access Token inside a custom HTTP authentication header?

我正在尝试使用 LibGit2Sharp 以编程方式将我在本地存储库上所做的提交推送到远程副本,该副本托管在私有 Azure DevOps 服务器上。

根据 Azure documentation,启用 HTTPS OAuth 的个人访问令牌需要在自定义身份验证 header 中与请求一起发送,如 'Basic' 和 Base64 编码令牌:

var personalaccesstoken = "PATFROMWEB";

    using (HttpClient client = new HttpClient()) {
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
            Convert.ToBase64String(Encoding.ASCII.GetBytes($":{personalaccesstoken}")));

        using (HttpResponseMessage response = client.GetAsync(
                    "https://dev.azure.com/{organization}/{project}/_apis/build/builds?api-version=5.0").Result) {
            response.EnsureSuccessStatusCode();
        }
    }

LibGit2Sharp.CloneOptions class has a FetchOptions field which in turn has a CustomHeaders array that can be used to inject the authentication header during the clone operation, like the following (as mentioned in this issue):

        CloneOptions cloneOptions = new() {
        CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials {
            Username = $"{USERNAME}",
            Password = $"{ACCESSTOKEN}"
        },
        FetchOptions = new FetchOptions {
            CustomHeaders = new[] {
                $"Authorization: Basic {encodedToken}"
            }
        }
    };
    Repository.Clone(AzureUrl, LocalDirectory, cloneOptions);
    

并且克隆过程成功(我测试了它以及 checked the source code :))

但是,LibGit2Sharp.PushOptions 没有任何此类机制来注入身份验证 header。我限于以下代码:

PushOptions pushOptions = new()
        {
            CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials
            {
                Username = $"{USERNAME}",
                Password = $"{PASSWORD}"
            }
        };

这使我的推送操作失败并显示以下消息:

Too many redirects or authentication replays

我检查了 Repository.Network.Push() on Github 的源代码。

public virtual void Push(Remote remote, IEnumerable<string> pushRefSpecs, PushOptions pushOptions)
    {
        Ensure.ArgumentNotNull(remote, "remote");
        Ensure.ArgumentNotNull(pushRefSpecs, "pushRefSpecs");

        // Return early if there is nothing to push.
        if (!pushRefSpecs.Any())
        {
            return;
        }

        if (pushOptions == null)
        {
            pushOptions = new PushOptions();
        }

        // Load the remote.
        using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, remote.Name, true))
        {
            var callbacks = new RemoteCallbacks(pushOptions);
            GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks();

            Proxy.git_remote_push(remoteHandle,
                                  pushRefSpecs,
                                  new GitPushOptions()
                                  {
                                      PackbuilderDegreeOfParallelism = pushOptions.PackbuilderDegreeOfParallelism,
                                      RemoteCallbacks = gitCallbacks,
                                      ProxyOptions = new GitProxyOptions { Version = 1 },
                                  });
        }
    }

正如我们在上面看到的,Push() 方法内部的 Proxy.git_remote_push 方法调用正在传递一个新的 GitPushOptions object,这确实 seems to have a CustomHeaders field implemented. 但是它没有暴露给消费者应用程序,而是直接在库代码中实例化!

我绝对有必要使用 LibGit2Sharp API,并且我们的 end-to-end 测试需要在 Azure DevOps 存储库上完成,所以这个问题阻碍了我的进一步发展。

我的问题是:

  1. 是否可以使用其他方式对来自 LibGit2Sharp 的 Azure 推送操作进行身份验证?我们能否利用 PushOptions.CredentialsProvider 处理程序,使其与 Azure 坚持的 auth-n 方法兼容?
  2. 我们可以通过在执行 Push 命令之前在 FetchOptions object 中注入 header 来调用 Commands.Fetch 来缓存凭据吗?我试过了,但失败并出现同样的错误。
  3. 为了解决这个问题,是否需要对库进行修改以使其与 Azure Repos 兼容?如果是,那么如果有人可以给我关于如何绑定到本机代码的指示,那么我可以加强并做出贡献:)

我会回答我自己的问题,因为我们已经解决了这个问题。

解决这个问题很简单;我只需要从 PushOptions 对象中删除 CredentialsProvider 委托,即:

var pushOptions = new PushOptions();

而不是,

PushOptions pushOptions = new()
        {
            CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials
            {
                Username = $"{USERNAME}",
                Password = $"{PASSWORD}"
            }
        };

¯\(ツ)

我不知道它为什么有效,但确实有效。 (也许 Azure 的一些人可以向我们澄清一下。)