Skip to content

Commit 837f39b

Browse files
authored
Add Kerberos support (#684)
<!-- For the checkboxes below you must check each one to indicate that you either did the relevant task, or considered it and decided there was nothing that needed doing --> Issue #678 Added support for kerberos authentication when calling BBS API's. To do this all we need to do is configure our HttpClient to have `UseDefaultCredentials = true` and NOT set the normal Authorization header that we set. This needs to be set at the time we construct the HttpClient. Similar to how we did the NoSSL support, I added a couple different named HttpClient's into our DI container ("Default" and "Kerberos") configured appropriately. Then I added a new `CreateKerberos()` function to `BbsApiFactory` that doesn't require a username/password. Finally I added a 2nd constructor to `BbsApi` that doesn't require a username/password and doesn't attempt to set the Authorization header. I added a `--kerberos` flag to both `bbs2gh generate-script` and `bbs2gh migrate-repo`. For now this is a hidden option. - [x] Did you write/update appropriate tests - [x] Release notes updated (if appropriate) - [x] Appropriate logging output - [x] Issue linked - [x] Docs updated (or issue created) <!-- For docs we should review the docs at: https://docs.github.com/en/early-access/github/migrating-with-github-enterprise-importer and the README.md in this repo If a doc update is required based on the changes in this PR, it is sufficient to create an issue and link to it here. The doc update can be made later/separately. The process to update the docs can be found here: https://github.com/github/docs-early-access#opening-prs The markdown files are here: https://github.com/github/docs-early-access/tree/main/content/github/migrating-with-github-enterprise-importer -->
1 parent e97ddd6 commit 837f39b

File tree

11 files changed

+262
-27
lines changed

11 files changed

+262
-27
lines changed

src/Octoshift/BbsClient.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@ public class BbsClient
1414
private readonly OctoLogger _log;
1515
private readonly RetryPolicy _retryPolicy;
1616

17-
public BbsClient(OctoLogger log, HttpClient httpClient, IVersionProvider versionProvider, RetryPolicy retryPolicy, string username, string password)
17+
public BbsClient(OctoLogger log, HttpClient httpClient, IVersionProvider versionProvider, RetryPolicy retryPolicy, string username, string password) :
18+
this(log, httpClient, versionProvider, retryPolicy)
19+
{
20+
if (_httpClient != null)
21+
{
22+
var authCredentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"));
23+
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authCredentials);
24+
}
25+
}
26+
27+
public BbsClient(OctoLogger log, HttpClient httpClient, IVersionProvider versionProvider, RetryPolicy retryPolicy)
1828
{
1929
_log = log;
2030
_httpClient = httpClient;
@@ -23,8 +33,6 @@ public BbsClient(OctoLogger log, HttpClient httpClient, IVersionProvider version
2333
if (_httpClient != null)
2434
{
2535
_httpClient.DefaultRequestHeaders.Add("accept", "application/json");
26-
var authCredentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{username}:{password}"));
27-
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authCredentials);
2836
_httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("OctoshiftCLI", versionProvider?.GetCurrentVersion()));
2937
if (versionProvider?.GetVersionComments() is { } comments)
3038
{

src/OctoshiftCLI.Tests/BbsClientTests.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ public void It_Adds_The_Authorization_Header()
5151
httpClient.DefaultRequestHeaders.Authorization.Scheme.Should().Be("Basic");
5252
}
5353

54+
[Fact]
55+
public void It_Doesnt_Add_Authorization_Header_When_No_Credentials_Passed()
56+
{
57+
// Arrange
58+
using var httpClient = new HttpClient(MockHttpHandlerForGet().Object);
59+
60+
// Act
61+
_ = new BbsClient(_mockOctoLogger.Object, httpClient, null, _retryPolicy);
62+
63+
// Assert
64+
httpClient.DefaultRequestHeaders.Authorization.Should().BeNull();
65+
}
66+
5467
[Fact]
5568
public async Task GetAsync_Encodes_The_Url()
5669
{
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System.Linq;
2+
using System.Net.Http;
3+
using FluentAssertions;
4+
using Moq;
5+
using OctoshiftCLI.BbsToGithub;
6+
using Xunit;
7+
8+
namespace OctoshiftCLI.Tests.bbs2gh.Commands
9+
{
10+
public class BbsApiFactoryTests
11+
{
12+
private const string BBS_SERVER_URL = "http://bbs.contoso.com:7990";
13+
14+
private readonly Mock<OctoLogger> _mockOctoLogger = TestHelpers.CreateMock<OctoLogger>();
15+
private readonly Mock<EnvironmentVariableProvider> _mockEnvironmentVariableProvider = TestHelpers.CreateMock<EnvironmentVariableProvider>();
16+
private readonly Mock<IHttpClientFactory> _mockHttpClientFactory = new Mock<IHttpClientFactory>();
17+
18+
private readonly BbsApiFactory _bbsApiFactory;
19+
20+
public BbsApiFactoryTests()
21+
{
22+
_bbsApiFactory = new BbsApiFactory(_mockOctoLogger.Object, _mockHttpClientFactory.Object, _mockEnvironmentVariableProvider.Object, null, null);
23+
}
24+
25+
[Fact]
26+
public void Should_Create_BbsApi_For_Source_Bbs_Api_With_Kerberos()
27+
{
28+
using var httpClient = new HttpClient();
29+
30+
_mockHttpClientFactory
31+
.Setup(x => x.CreateClient("Kerberos"))
32+
.Returns(httpClient);
33+
34+
// Act
35+
var githubApi = _bbsApiFactory.CreateKerberos(BBS_SERVER_URL);
36+
37+
// Assert
38+
githubApi.Should().NotBeNull();
39+
httpClient.DefaultRequestHeaders.Accept.First().MediaType.Should().Be("application/json");
40+
}
41+
42+
[Fact]
43+
public void Should_Create_BbsApi_For_Source_Bbs_Api_With_Default()
44+
{
45+
using var httpClient = new HttpClient();
46+
47+
_mockHttpClientFactory
48+
.Setup(x => x.CreateClient("Default"))
49+
.Returns(httpClient);
50+
51+
// Act
52+
var githubApi = _bbsApiFactory.Create(BBS_SERVER_URL, "user", "pass");
53+
54+
// Assert
55+
githubApi.Should().NotBeNull();
56+
httpClient.DefaultRequestHeaders.Accept.First().MediaType.Should().Be("application/json");
57+
}
58+
}
59+
}
Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,84 @@
1+
using System;
12
using FluentAssertions;
3+
using Moq;
4+
using OctoshiftCLI.BbsToGithub;
25
using OctoshiftCLI.BbsToGithub.Commands;
6+
using OctoshiftCLI.Contracts;
37
using Xunit;
48

59
namespace OctoshiftCLI.Tests.BbsToGithub.Commands;
610

711
public class GenerateScriptCommandTests
812
{
13+
private const string BBS_SERVER_URL = "http://bbs.contoso.com:7990";
14+
15+
private readonly Mock<IServiceProvider> _mockServiceProvider = new();
16+
private readonly Mock<BbsApiFactory> _mockBbsApiFactory = TestHelpers.CreateMock<BbsApiFactory>();
17+
private readonly Mock<OctoLogger> _mockOctoLogger = TestHelpers.CreateMock<OctoLogger>();
18+
private readonly Mock<EnvironmentVariableProvider> _mockEnvironmentVariableProvider = TestHelpers.CreateMock<EnvironmentVariableProvider>();
19+
private readonly Mock<FileSystemProvider> _mockFileSystemProvider = TestHelpers.CreateMock<FileSystemProvider>();
20+
private readonly Mock<IVersionProvider> _mockVersionProvider = new();
21+
22+
private readonly GenerateScriptCommand _command = new();
23+
24+
public GenerateScriptCommandTests()
25+
{
26+
_mockServiceProvider.Setup(m => m.GetService(typeof(OctoLogger))).Returns(_mockOctoLogger.Object);
27+
_mockServiceProvider.Setup(m => m.GetService(typeof(EnvironmentVariableProvider))).Returns(_mockEnvironmentVariableProvider.Object);
28+
_mockServiceProvider.Setup(m => m.GetService(typeof(FileSystemProvider))).Returns(_mockFileSystemProvider.Object);
29+
_mockServiceProvider.Setup(m => m.GetService(typeof(IVersionProvider))).Returns(_mockVersionProvider.Object);
30+
_mockServiceProvider.Setup(m => m.GetService(typeof(BbsApiFactory))).Returns(_mockBbsApiFactory.Object);
31+
}
32+
933
[Fact]
1034
public void Should_Have_Options()
1135
{
12-
var command = new GenerateScriptCommand();
13-
command.Should().NotBeNull();
14-
command.Name.Should().Be("generate-script");
15-
command.Options.Count.Should().Be(10);
16-
17-
TestHelpers.VerifyCommandOption(command.Options, "bbs-server-url", true);
18-
TestHelpers.VerifyCommandOption(command.Options, "github-org", true);
19-
TestHelpers.VerifyCommandOption(command.Options, "bbs-username", false);
20-
TestHelpers.VerifyCommandOption(command.Options, "bbs-password", false);
21-
TestHelpers.VerifyCommandOption(command.Options, "bbs-shared-home", false);
22-
TestHelpers.VerifyCommandOption(command.Options, "ssh-user", false);
23-
TestHelpers.VerifyCommandOption(command.Options, "ssh-private-key", false);
24-
TestHelpers.VerifyCommandOption(command.Options, "ssh-port", false);
25-
TestHelpers.VerifyCommandOption(command.Options, "output", false);
26-
TestHelpers.VerifyCommandOption(command.Options, "verbose", false);
36+
_command.Should().NotBeNull();
37+
_command.Name.Should().Be("generate-script");
38+
_command.Options.Count.Should().Be(11);
39+
40+
TestHelpers.VerifyCommandOption(_command.Options, "bbs-server-url", true);
41+
TestHelpers.VerifyCommandOption(_command.Options, "github-org", true);
42+
TestHelpers.VerifyCommandOption(_command.Options, "bbs-username", false);
43+
TestHelpers.VerifyCommandOption(_command.Options, "bbs-password", false);
44+
TestHelpers.VerifyCommandOption(_command.Options, "bbs-shared-home", false);
45+
TestHelpers.VerifyCommandOption(_command.Options, "ssh-user", false);
46+
TestHelpers.VerifyCommandOption(_command.Options, "ssh-private-key", false);
47+
TestHelpers.VerifyCommandOption(_command.Options, "ssh-port", false);
48+
TestHelpers.VerifyCommandOption(_command.Options, "output", false);
49+
TestHelpers.VerifyCommandOption(_command.Options, "kerberos", false, true);
50+
TestHelpers.VerifyCommandOption(_command.Options, "verbose", false);
51+
}
52+
53+
[Fact]
54+
public void It_Gets_A_Kerberos_HttpClient_When_Kerberos_Is_True()
55+
{
56+
var args = new GenerateScriptCommandArgs
57+
{
58+
BbsServerUrl = BBS_SERVER_URL,
59+
Kerberos = true
60+
};
61+
62+
_command.BuildHandler(args, _mockServiceProvider.Object);
63+
64+
_mockBbsApiFactory.Verify(m => m.CreateKerberos(BBS_SERVER_URL));
65+
}
66+
67+
[Fact]
68+
public void It_Gets_A_Default_HttpClient_When_Kerberos_Is_Not_Set()
69+
{
70+
var bbsTestUser = "user";
71+
var bbsTestPassword = "password";
72+
73+
var args = new GenerateScriptCommandArgs
74+
{
75+
BbsServerUrl = BBS_SERVER_URL,
76+
BbsUsername = bbsTestUser,
77+
BbsPassword = bbsTestPassword,
78+
};
79+
80+
_command.BuildHandler(args, _mockServiceProvider.Object);
81+
82+
_mockBbsApiFactory.Verify(m => m.Create(BBS_SERVER_URL, bbsTestUser, bbsTestPassword));
2783
}
2884
}

src/OctoshiftCLI.Tests/bbs2gh/Commands/MigrateRepoCommandTests.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public void Should_Have_Options()
4949
var command = new MigrateRepoCommand();
5050
command.Should().NotBeNull();
5151
command.Name.Should().Be("migrate-repo");
52-
command.Options.Count.Should().Be(22);
52+
command.Options.Count.Should().Be(23);
5353

5454
TestHelpers.VerifyCommandOption(command.Options, "bbs-server-url", false);
5555
TestHelpers.VerifyCommandOption(command.Options, "bbs-project", false);
@@ -71,6 +71,7 @@ public void Should_Have_Options()
7171
TestHelpers.VerifyCommandOption(command.Options, "smb-user", false, true);
7272
TestHelpers.VerifyCommandOption(command.Options, "smb-password", false, true);
7373
TestHelpers.VerifyCommandOption(command.Options, "wait", false);
74+
TestHelpers.VerifyCommandOption(command.Options, "kerberos", false, true);
7475
TestHelpers.VerifyCommandOption(command.Options, "verbose", false);
7576
}
7677

@@ -188,4 +189,33 @@ public void BuildHandler_Creates_Azure_Api_Factory_When_Azure_Storage_Connection
188189

189190
_mockAzureApiFactory.Verify(m => m.Create(AZURE_STORAGE_CONNECTION_STRING));
190191
}
192+
193+
[Fact]
194+
public void It_Gets_A_Kerberos_HttpClient_When_Kerberos_Is_True()
195+
{
196+
var args = new MigrateRepoCommandArgs
197+
{
198+
BbsServerUrl = BBS_SERVER_URL,
199+
Kerberos = true
200+
};
201+
202+
_command.BuildHandler(args, _mockServiceProvider.Object);
203+
204+
_mockBbsApiFactory.Verify(m => m.CreateKerberos(BBS_SERVER_URL));
205+
}
206+
207+
[Fact]
208+
public void It_Gets_A_Default_HttpClient_When_Kerberos_Is_Not_Set()
209+
{
210+
var args = new MigrateRepoCommandArgs
211+
{
212+
BbsServerUrl = BBS_SERVER_URL,
213+
BbsUsername = BBS_USERNAME,
214+
BbsPassword = BBS_PASSWORD,
215+
};
216+
217+
_command.BuildHandler(args, _mockServiceProvider.Object);
218+
219+
_mockBbsApiFactory.Verify(m => m.Create(BBS_SERVER_URL, BBS_USERNAME, BBS_PASSWORD));
220+
}
191221
}

src/OctoshiftCLI.Tests/bbs2gh/Handlers/GenerateScriptCommandHandlerTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,42 @@ public async Task Two_Projects_Two_Repos_Each_All_Options()
152152
_mockEnvironmentVariableProvider.Verify(m => m.BbsPassword(), Times.Never);
153153
}
154154

155+
[Fact]
156+
public async Task One_Repo_With_Kerberos()
157+
{
158+
// Arrange
159+
_mockBbsApi.Setup(m => m.GetProjects()).ReturnsAsync(new[]
160+
{
161+
(Id: 1, Key: BBS_FOO_PROJECT_KEY, Name: BBS_FOO_PROJECT_NAME),
162+
});
163+
_mockBbsApi.Setup(m => m.GetRepos(BBS_FOO_PROJECT_KEY)).ReturnsAsync(new[]
164+
{
165+
(Id: 1, Slug: BBS_FOO_REPO_1_SLUG, Name: BBS_FOO_REPO_1_NAME),
166+
});
167+
168+
const string migrateRepoCommand = $"Exec {{ gh bbs2gh migrate-repo --bbs-server-url \"{BBS_SERVER_URL}\" --bbs-username \"{BBS_USERNAME}\" --bbs-shared-home \"{BBS_SHARED_HOME}\" --bbs-project \"{BBS_FOO_PROJECT_KEY}\" --bbs-repo \"{BBS_FOO_REPO_1_SLUG}\" --ssh-user \"{SSH_USER}\" --ssh-private-key \"{SSH_PRIVATE_KEY}\" --ssh-port {SSH_PORT} --github-org \"{GITHUB_ORG}\" --github-repo \"{BBS_FOO_PROJECT_KEY}-{BBS_FOO_REPO_1_SLUG}\" --verbose --wait --kerberos }}";
169+
170+
// Act
171+
var args = new GenerateScriptCommandArgs
172+
{
173+
BbsServerUrl = BBS_SERVER_URL,
174+
GithubOrg = GITHUB_ORG,
175+
BbsUsername = BBS_USERNAME,
176+
BbsPassword = BBS_PASSWORD,
177+
BbsSharedHome = BBS_SHARED_HOME,
178+
SshUser = SSH_USER,
179+
SshPrivateKey = SSH_PRIVATE_KEY,
180+
SshPort = SSH_PORT,
181+
Output = new FileInfo(OUTPUT),
182+
Verbose = true,
183+
Kerberos = true,
184+
};
185+
await _handler.Handle(args);
186+
187+
// Assert
188+
_mockFileSystemProvider.Verify(m => m.WriteAllTextAsync(It.IsAny<string>(), It.Is<string>(script => script.Contains(migrateRepoCommand))));
189+
}
190+
155191
[Fact]
156192
public async Task Generated_Script_Contains_The_Cli_Version_Comment()
157193
{

src/bbs2gh/BbsApiFactory.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ namespace OctoshiftCLI.BbsToGithub
66
public class BbsApiFactory
77
{
88
private readonly OctoLogger _octoLogger;
9-
private readonly HttpClient _client;
9+
private readonly IHttpClientFactory _clientFactory;
1010
private readonly EnvironmentVariableProvider _environmentVariableProvider;
1111
private readonly IVersionProvider _versionProvider;
1212
private readonly RetryPolicy _retryPolicy;
1313

14-
public BbsApiFactory(OctoLogger octoLogger, HttpClient client, EnvironmentVariableProvider environmentVariableProvider, IVersionProvider versionProvider, RetryPolicy retryPolicy)
14+
public BbsApiFactory(OctoLogger octoLogger, IHttpClientFactory clientFactory, EnvironmentVariableProvider environmentVariableProvider, IVersionProvider versionProvider, RetryPolicy retryPolicy)
1515
{
1616
_octoLogger = octoLogger;
17-
_client = client;
17+
_clientFactory = clientFactory;
1818
_environmentVariableProvider = environmentVariableProvider;
1919
_versionProvider = versionProvider;
2020
_retryPolicy = retryPolicy;
@@ -25,7 +25,17 @@ public virtual BbsApi Create(string bbsServerUrl, string bbsUsername, string bbs
2525
bbsUsername ??= _environmentVariableProvider.BbsUsername();
2626
bbsPassword ??= _environmentVariableProvider.BbsPassword();
2727

28-
var bbsClient = new BbsClient(_octoLogger, _client, _versionProvider, _retryPolicy, bbsUsername, bbsPassword);
28+
var httpClient = _clientFactory.CreateClient("Default");
29+
30+
var bbsClient = new BbsClient(_octoLogger, httpClient, _versionProvider, _retryPolicy, bbsUsername, bbsPassword);
31+
return new BbsApi(bbsClient, bbsServerUrl, _octoLogger);
32+
}
33+
34+
public virtual BbsApi CreateKerberos(string bbsServerUrl)
35+
{
36+
var httpClient = _clientFactory.CreateClient("Kerberos");
37+
38+
var bbsClient = new BbsClient(_octoLogger, httpClient, _versionProvider, _retryPolicy);
2939
return new BbsApi(bbsClient, bbsServerUrl, _octoLogger);
3040
}
3141
}

src/bbs2gh/Commands/GenerateScriptCommand.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public GenerateScriptCommand() : base(
2323
AddOption(SshPrivateKey);
2424
AddOption(SshPort);
2525
AddOption(Output);
26+
AddOption(Kerberos);
2627
AddOption(Verbose);
2728
}
2829

@@ -65,6 +66,11 @@ public GenerateScriptCommand() : base(
6566
name: "--output",
6667
getDefaultValue: () => new FileInfo("./migrate.ps1"));
6768

69+
public Option<bool> Kerberos { get; } = new(
70+
name: "--kerberos",
71+
description: "Use Kerberos authentication for Bitbucket Server.")
72+
{ IsHidden = true };
73+
6874
public Option<bool> Verbose { get; } = new("--verbose");
6975

7076
public override GenerateScriptCommandHandler BuildHandler(GenerateScriptCommandArgs args, IServiceProvider sp)
@@ -85,7 +91,7 @@ public override GenerateScriptCommandHandler BuildHandler(GenerateScriptCommandA
8591
var environmentVariableProvider = sp.GetRequiredService<EnvironmentVariableProvider>();
8692

8793
var bbsApiFactory = sp.GetRequiredService<BbsApiFactory>();
88-
var bbsApi = bbsApiFactory.Create(args.BbsServerUrl, args.BbsUsername, args.BbsPassword);
94+
var bbsApi = args.Kerberos ? bbsApiFactory.CreateKerberos(args.BbsServerUrl) : bbsApiFactory.Create(args.BbsServerUrl, args.BbsUsername, args.BbsPassword);
8995

9096
return new GenerateScriptCommandHandler(log, versionProvider, fileSystemProvider, bbsApi, environmentVariableProvider);
9197
}
@@ -102,5 +108,6 @@ public class GenerateScriptCommandArgs
102108
public string SshPrivateKey { get; set; }
103109
public string SshPort { get; set; }
104110
public FileInfo Output { get; set; }
111+
public bool Kerberos { get; set; }
105112
public bool Verbose { get; set; }
106113
}

0 commit comments

Comments
 (0)