Skip to content

Commit 28ec2ee

Browse files
committed
enhance configuraiton for authentication providers
motivation: support custom authentican providers, not just netrc changes: * reafactor and seperate out the auth data model from the loading/saving mutation utility * adjust call sites * add tests
1 parent 50fc7f2 commit 28ec2ee

File tree

6 files changed

+141
-23
lines changed

6 files changed

+141
-23
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
Copyright (c) 2021 Apple Inc. and the Swift project authors
4+
Licensed under Apache License v2.0 with Runtime Library Exception
5+
See http://swift.org/LICENSE.txt for license information
6+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
7+
*/
8+
9+
import Foundation
10+
11+
public protocol AuthorizationProvider {
12+
func authentication(for url: URL) -> (user: String, password: String)?
13+
}
14+
15+
extension AuthorizationProvider {
16+
public func httpAuthorizationHeader(for url: URL) -> String? {
17+
guard let (user, password) = self.authentication(for: url) else {
18+
return nil
19+
}
20+
let authString = "\(user):\(password)"
21+
guard let authData = authString.data(using: .utf8) else {
22+
return nil
23+
}
24+
return "Basic \(authData.base64EncodedString())"
25+
}
26+
}

Sources/Basics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_library(Basics
10+
AuthorizationProvider.swift
1011
ByteString+Extensions.swift
1112
ConcurrencyHelpers.swift
1213
Dictionary+Extensions.swift

Sources/Commands/SwiftTool.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -508,22 +508,32 @@ public class SwiftTool {
508508
return newPath
509509
}
510510

511+
func getAuthorizationProvider() throws -> AuthorizationProvider? {
512+
// currently only single provider: netrc
513+
return try self.getNetrcConfig()?.get()
514+
}
515+
516+
func getNetrcConfig() throws -> Workspace.Configuration.Netrc? {
517+
guard options.netrc || options.netrcFilePath != nil || options.netrcOptional else {
518+
return .none
519+
}
520+
521+
let netrcFilePath = try self.netrcFilePath()
522+
return netrcFilePath.map { .init(path: $0, fileSystem: localFileSystem) }
523+
}
524+
511525
private func netrcFilePath() throws -> AbsolutePath? {
512-
guard options.netrc ||
513-
options.netrcFilePath != nil ||
514-
options.netrcOptional else { return nil }
515-
516-
let resolvedPath: AbsolutePath = options.netrcFilePath ?? AbsolutePath("\(NSHomeDirectory())/.netrc")
517-
guard localFileSystem.exists(resolvedPath) else {
526+
let netrcFilePath = options.netrcFilePath ?? localFileSystem.homeDirectory.appending(component: ".netrc")
527+
guard localFileSystem.exists(netrcFilePath) else {
518528
if !options.netrcOptional {
519-
diagnostics.emit(error: "Cannot find mandatory .netrc file at \(resolvedPath.pathString). To make .netrc file optional, use --netrc-optional flag.")
529+
diagnostics.emit(error: "Cannot find mandatory .netrc file at \(netrcFilePath). To make .netrc file optional, use --netrc-optional flag.")
520530
throw ExitCode.failure
521531
} else {
522-
diagnostics.emit(warning: "Did not find optional .netrc file at \(resolvedPath.pathString).")
532+
diagnostics.emit(warning: "Did not find optional .netrc file at \(netrcFilePath).")
523533
return .none
524534
}
525535
}
526-
return resolvedPath
536+
return netrcFilePath
527537
}
528538

529539
private func getSharedCacheDirectory() throws -> AbsolutePath? {
@@ -582,7 +592,7 @@ public class SwiftTool {
582592
sharedConfigurationDirectory: sharedConfigurationDirectory
583593
),
584594
mirrors: self.getMirrorsConfig(sharedConfigurationDirectory: sharedConfigurationDirectory).mirrors,
585-
netrcFilePath: self.netrcFilePath(),
595+
authorizationProvider: self.getAuthorizationProvider(),
586596
customManifestLoader: self.getManifestLoader(), // FIXME: doe we really need to customize it?
587597
customRepositoryProvider: provider, // FIXME: doe we really need to customize it?
588598
additionalFileRules: isXcodeBuildSystemEnabled ? FileRuleDescription.xcbuildFileTypes : FileRuleDescription.swiftpmFileTypes,

Sources/Workspace/Workspace.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public class Workspace {
200200
/// The http client used for downloading binary artifacts.
201201
fileprivate let httpClient: HTTPClient
202202

203-
fileprivate let netrcFilePath: AbsolutePath?
203+
fileprivate let authorizationProvider: AuthorizationProvider?
204204

205205
/// The downloader used for unarchiving binary artifacts.
206206
fileprivate let archiver: Archiver
@@ -239,7 +239,7 @@ public class Workspace {
239239
/// - fileSystem: The file system to use.
240240
/// - location: Workspace location configuration.
241241
/// - mirrors: Dependencies mirrors.
242-
/// - netrcFilePath: Path tot he netrc file.
242+
/// - authorizationProvider: Provider of authentication information.
243243
/// - customToolsVersion: A custom tools version.
244244
/// - customManifestLoader: A custom manifest loader.
245245
/// - customRepositoryManager: A custom repository manager.
@@ -249,15 +249,16 @@ public class Workspace {
249249
/// - customArchiver: A custom archiver.
250250
/// - customChecksumAlgorithm: A custom checksum algorithm.
251251
/// - additionalFileRules: File rules to determine resource handling behavior.
252-
/// - resolverUpdateEnabled: Enables the dependencies resolver automatic version update check, relying only on the resolved version file
253-
/// - resolverPrefetchingEnabled: Enables the dependencies resolver prefetching based on the resolved version file
254-
/// - resolverTracingEnabled:Enables the dependencies resolver tracing
252+
/// - resolverUpdateEnabled: Enables the dependencies resolver automatic version update check. Enabled by default. When disabled the resolver relies only on the resolved version file
253+
/// - resolverPrefetchingEnabled: Enables the dependencies resolver prefetching based on the resolved version file. Enabled by default..
254+
/// - resolverTracingEnabled: Enables the dependencies resolver tracing. Disabled by default..
255+
/// - sharedRepositoriesCacheEnabled: Enables the shared repository cache. Enabled by default..
255256
/// - delegate: Delegate for workspace events
256257
public init(
257258
fileSystem: FileSystem,
258259
location: Location,
259260
mirrors: DependencyMirrors? = .none,
260-
netrcFilePath: AbsolutePath? = .none,
261+
authorizationProvider: AuthorizationProvider? = .none,
261262
customToolsVersion: ToolsVersion? = .none,
262263
customManifestLoader: ManifestLoaderProtocol? = .none,
263264
customRepositoryManager: RepositoryManager? = .none,
@@ -311,7 +312,7 @@ public class Workspace {
311312
self.location = location
312313
self.delegate = delegate
313314
self.mirrors = mirrors
314-
self.netrcFilePath = netrcFilePath
315+
self.authorizationProvider = authorizationProvider
315316
self.manifestLoader = manifestLoader
316317
self.currentToolsVersion = currentToolsVersion
317318
self.toolsVersionLoader = toolsVersionLoader
@@ -380,7 +381,9 @@ public class Workspace {
380381
sharedConfigurationDirectory: nil // legacy
381382
),
382383
mirrors: config?.mirrors,
383-
netrcFilePath: netrcFilePath,
384+
authorizationProvider: netrcFilePath.map {
385+
try Configuration.Netrc(path: $0, fileSystem: fileSystem).get()
386+
},
384387
customToolsVersion: currentToolsVersion,
385388
customManifestLoader: manifestLoader,
386389
customRepositoryManager: repositoryManager,
@@ -1771,9 +1774,6 @@ extension Workspace {
17711774
let tempDiagnostics = DiagnosticsEngine()
17721775
let result = ThreadSafeArrayStore<ManagedArtifact>()
17731776

1774-
// FIXME: should this handle the error more gracefully?
1775-
let authProvider: AuthorizationProviding? = try? Netrc.load(fromFileAtPath: netrcFilePath).get()
1776-
17771777
// zip files to download
17781778
// stored in a thread-safe way as we may fetch more from "artifactbundleindex" files
17791779
let zipArtifacts = ThreadSafeArrayStore<RemoteArtifact>(artifacts.filter { $0.url.pathExtension.lowercased() == "zip" })
@@ -1787,7 +1787,7 @@ extension Workspace {
17871787
group.enter()
17881788
var request = HTTPClient.Request(method: .get, url: indexFile.url)
17891789
request.options.validResponseCodes = [200]
1790-
request.options.authorizationProvider = authProvider?.authorization(for:)
1790+
request.options.authorizationProvider = self.authorizationProvider?.httpAuthorizationHeader(for:)
17911791
self.httpClient.execute(request) { result in
17921792
defer { group.leave() }
17931793

@@ -1858,7 +1858,7 @@ extension Workspace {
18581858

18591859
group.enter()
18601860
var request = HTTPClient.Request.download(url: artifact.url, fileSystem: self.fileSystem, destination: archivePath)
1861-
request.options.authorizationProvider = authProvider?.authorization(for:)
1861+
request.options.authorizationProvider = self.authorizationProvider?.httpAuthorizationHeader(for:)
18621862
request.options.validResponseCodes = [200]
18631863
self.httpClient.execute(
18641864
request,

Sources/Workspace/WorkspaceConfiguration.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,52 @@ extension Workspace.Configuration {
315315
}
316316
}
317317

318+
// MARK: - Authentication
319+
320+
extension Workspace.Configuration {
321+
public struct Netrc {
322+
private let path: AbsolutePath
323+
private let fileSystem: FileSystem
324+
325+
public init(path: AbsolutePath, fileSystem: FileSystem) {
326+
self.path = path
327+
self.fileSystem = fileSystem
328+
}
329+
330+
public func get() throws -> AuthorizationProvider {
331+
return try Self.load(self.path, fileSystem: self.fileSystem)
332+
}
333+
334+
private static func load(_ path: AbsolutePath, fileSystem: FileSystem) throws -> AuthorizationProvider {
335+
let netrc = try TSCUtility.Netrc.load(fromFileAtPath: path).get()
336+
return NetrcAuthorizationProvider(netrc)
337+
}
338+
339+
struct NetrcAuthorizationProvider: AuthorizationProvider {
340+
private let underlying: TSCUtility.Netrc
341+
342+
init(_ underlying: TSCUtility.Netrc) {
343+
self.underlying = underlying
344+
}
345+
346+
func authentication(for url: Foundation.URL) -> (user: String, password: String)? {
347+
return self.machine(for: url).map { (user: $0.login, password: $0.password) }
348+
}
349+
350+
private func machine(for url: Foundation.URL) -> TSCUtility.Netrc.Machine? {
351+
if let machine = self.underlying.machines.first(where: { $0.name.lowercased() == url.host?.lowercased() }) {
352+
return machine
353+
}
354+
if let machine = self.underlying.machines.first(where: { $0.isDefault }) {
355+
return machine
356+
}
357+
return .none
358+
}
359+
}
360+
}
361+
}
362+
363+
318364
// MARK: - Deprecated 8/20201
319365

320366
extension Workspace {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright (c) 2021 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Basics
12+
import TSCBasic
13+
import XCTest
14+
15+
final class AuthorizationProviderTests: XCTestCase {
16+
func testBasicAPIs() {
17+
struct TestProvider: AuthorizationProvider {
18+
let map: [URL: (user: String, password: String)]
19+
20+
func authentication(for url: URL) -> (user: String, password: String)? {
21+
return self.map[url]
22+
}
23+
}
24+
25+
let url = URL(string: "http://\(UUID().uuidString)")!
26+
let user = UUID().uuidString
27+
let password = UUID().uuidString
28+
let provider = TestProvider(map: [url: (user: user, password: password)])
29+
30+
let auth = provider.authentication(for: url)
31+
XCTAssertEqual(auth?.user, user)
32+
XCTAssertEqual(auth?.password, password)
33+
XCTAssertEqual(provider.httpAuthorizationHeader(for: url), "Basic " + "\(user):\(password)".data(using: .utf8)!.base64EncodedString())
34+
}
35+
}

0 commit comments

Comments
 (0)