Skip to content

Commit 61d1c60

Browse files
committed
Add CompositeAuthorizationProvider
1 parent bd1a5bf commit 61d1c60

File tree

3 files changed

+76
-10
lines changed

3 files changed

+76
-10
lines changed

Sources/Basics/AuthorizationProvider.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ extension Foundation.URL {
5353
// MARK: - netrc
5454

5555
public struct NetrcAuthorizationProvider: AuthorizationProvider {
56-
private let path: AbsolutePath
56+
let path: AbsolutePath
5757
private let fileSystem: FileSystem
5858

5959
private var underlying: TSCUtility.Netrc?
@@ -248,3 +248,34 @@ public struct KeychainAuthorizationProvider: AuthorizationProvider {
248248
}
249249
}
250250
#endif
251+
252+
// MARK: - Composite
253+
254+
public struct CompositeAuthorizationProvider: AuthorizationProvider {
255+
private let providers: [AuthorizationProvider]
256+
257+
public init(_ providers: AuthorizationProvider...) {
258+
self.init(providers)
259+
}
260+
261+
public init(_ providers: [AuthorizationProvider]) {
262+
self.providers = providers
263+
}
264+
265+
public func authentication(for url: Foundation.URL) -> (user: String, password: String)? {
266+
for provider in self.providers {
267+
if let authentication = provider.authentication(for: url) {
268+
switch provider {
269+
case let provider as NetrcAuthorizationProvider:
270+
ObservabilitySystem.topScope.emit(info: "Credentials for \(url) found in netrc file at \(provider.path)")
271+
case is KeychainAuthorizationProvider:
272+
ObservabilitySystem.topScope.emit(info: "Credentials for \(url) found in keychain")
273+
default:
274+
ObservabilitySystem.topScope.emit(info: "Credentials for \(url) found in \(provider)")
275+
}
276+
return authentication
277+
}
278+
}
279+
return nil
280+
}
281+
}

Sources/Workspace/WorkspaceConfiguration.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,13 @@ extension Workspace.Configuration {
350350
}
351351

352352
private static func load(_ path: AbsolutePath, fileSystem: FileSystem) throws -> AuthorizationProvider {
353-
return try NetrcAuthorizationProvider(path: path, fileSystem: fileSystem)
353+
var providers = [AuthorizationProvider]()
354+
// netrc file has higher specificity than keychain so use it first
355+
providers.append(try NetrcAuthorizationProvider(path: path, fileSystem: fileSystem))
356+
#if canImport(Security)
357+
providers.append(KeychainAuthorizationProvider())
358+
#endif
359+
return CompositeAuthorizationProvider(providers)
354360
}
355361
}
356362
}

Tests/BasicsTests/AuthorizationProviderTests.swift

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,6 @@ import TSCTestSupport
1616

1717
final class AuthorizationProviderTests: XCTestCase {
1818
func testBasicAPIs() {
19-
struct TestProvider: AuthorizationProvider {
20-
let map: [URL: (user: String, password: String)]
21-
22-
func authentication(for url: URL) -> (user: String, password: String)? {
23-
return self.map[url]
24-
}
25-
}
26-
2719
let url = URL(string: "http://\(UUID().uuidString)")!
2820
let user = UUID().uuidString
2921
let password = UUID().uuidString
@@ -98,10 +90,47 @@ final class AuthorizationProviderTests: XCTestCase {
9890
#endif
9991
}
10092

93+
func testComposite() throws {
94+
let url = URL(string: "http://\(UUID().uuidString)")!
95+
let user = UUID().uuidString
96+
let passwordOne = UUID().uuidString
97+
let passwordTwo = UUID().uuidString
98+
99+
let providerOne = TestProvider(map: [url: (user: user, password: passwordOne)])
100+
let providerTwo = TestProvider(map: [url: (user: user, password: passwordTwo)])
101+
102+
do {
103+
// providerOne's password is returned first
104+
let provider = CompositeAuthorizationProvider(providerOne, providerTwo)
105+
self.assertAuthentication(provider, for: url, expected: (user, passwordOne))
106+
}
107+
108+
do {
109+
// providerTwo's password is returned first
110+
let provider = CompositeAuthorizationProvider(providerTwo, providerOne)
111+
self.assertAuthentication(provider, for: url, expected: (user, passwordTwo))
112+
}
113+
114+
do {
115+
// Neither has password
116+
let unknownURL = URL(string: "http://\(UUID().uuidString)")!
117+
let provider = CompositeAuthorizationProvider(providerOne, providerTwo)
118+
XCTAssertNil(provider.authentication(for: unknownURL))
119+
}
120+
}
121+
101122
private func assertAuthentication(_ provider: AuthorizationProvider, for url: Foundation.URL, expected: (user: String, password: String)) {
102123
let authentication = provider.authentication(for: url)
103124
XCTAssertEqual(authentication?.user, expected.user)
104125
XCTAssertEqual(authentication?.password, expected.password)
105126
XCTAssertEqual(provider.httpAuthorizationHeader(for: url), "Basic " + "\(expected.user):\(expected.password)".data(using: .utf8)!.base64EncodedString())
106127
}
107128
}
129+
130+
private struct TestProvider: AuthorizationProvider {
131+
let map: [URL: (user: String, password: String)]
132+
133+
func authentication(for url: URL) -> (user: String, password: String)? {
134+
return self.map[url]
135+
}
136+
}

0 commit comments

Comments
 (0)