Skip to content

Commit 85189ac

Browse files
authored
Update auth logic (#3789)
Motivation: SwiftPM should support using credentials stored in keychain for binary download and registry authentication. Modifications: - Wire up `KeychainAuthorizationProvider` in `SwiftTool`. Add `--no-keychain` option which allows user to opt-out. - Update netrc logic to either: - Load from a user-defined `.netrc` file - Default to `.netrc` file found in current workspace (if any) and/or user's home directory. - Deprecate `Workspace.Configuration.Netrc` rdar://83682028
1 parent d24449e commit 85189ac

File tree

6 files changed

+103
-36
lines changed

6 files changed

+103
-36
lines changed

Sources/Basics/AuthorizationProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public extension AuthorizationProvider {
4141
}
4242
}
4343

44-
extension Foundation.URL {
44+
private extension Foundation.URL {
4545
var authenticationID: String? {
4646
guard let host = host?.lowercased() else {
4747
return nil

Sources/Commands/Options.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,20 @@ public struct SwiftToolOptions: ParsableArguments {
328328
help: "Specify the .netrc file path.",
329329
completion: .file())
330330
var netrcFilePath: AbsolutePath?
331+
332+
/// Whether to use keychain for authenticating with remote servers
333+
/// when downloading binary artifacts or communicating with a registry.
334+
#if canImport(Security)
335+
@Flag(inversion: .prefixedEnableDisable,
336+
exclusivity: .exclusive,
337+
help: "Search credentials in macOS keychain")
338+
var keychain: Bool = true
339+
#else
340+
@Flag(inversion: .prefixedEnableDisable,
341+
exclusivity: .exclusive,
342+
help: .hidden)
343+
var keychain: Bool = false
344+
#endif
331345

332346
@Flag(name: .customLong("netrc"), help: .hidden)
333347
var _deprecated_netrc: Bool = false

Sources/Commands/SwiftTool.swift

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
4+
Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See http://swift.org/LICENSE.txt for license information
@@ -497,39 +497,60 @@ public class SwiftTool {
497497

498498
func getAuthorizationProvider() throws -> AuthorizationProvider? {
499499
var providers = [AuthorizationProvider]()
500+
500501
// netrc file has higher specificity than keychain so use it first
501-
if let netrcConfigFile = try self.getNetrcConfigFile() {
502-
providers.append(try NetrcAuthorizationProvider(path: netrcConfigFile, fileSystem: localFileSystem))
502+
try providers.append(contentsOf: self.getNetrcAuthorizationProviders())
503+
504+
#if canImport(Security)
505+
if self.options.keychain {
506+
providers.append(KeychainAuthorizationProvider(observabilityScope: self.observabilityScope))
503507
}
504-
505-
// TODO: add --no-keychain option to allow opt-out
506-
//#if canImport(Security)
507-
// providers.append(KeychainAuthorizationProvider(observabilityScope: self.observabilityScope))
508-
//#endif
508+
#endif
509509

510510
return providers.isEmpty ? .none : CompositeAuthorizationProvider(providers, observabilityScope: self.observabilityScope)
511511
}
512512

513-
func getNetrcConfigFile() throws -> AbsolutePath? {
513+
func getNetrcAuthorizationProviders() throws -> [NetrcAuthorizationProvider] {
514514
guard options.netrc else {
515-
return .none
515+
return []
516516
}
517517

518+
var providers = [NetrcAuthorizationProvider]()
519+
520+
// Use custom .netrc file if specified, otherwise look for it within workspace and user's home directory.
518521
if let configuredPath = options.netrcFilePath {
519522
guard localFileSystem.exists(configuredPath) else {
520523
throw StringError("Did not find .netrc file at \(configuredPath).")
521524
}
522-
return configuredPath
523-
}
525+
526+
providers.append(try NetrcAuthorizationProvider(path: configuredPath, fileSystem: localFileSystem))
527+
} else {
528+
// User didn't tell us to use these .netrc files so be more lenient with errors
529+
func loadNetrcNoThrows(at path: AbsolutePath) -> NetrcAuthorizationProvider? {
530+
guard localFileSystem.exists(path) else { return nil }
531+
532+
do {
533+
return try NetrcAuthorizationProvider(path: path, fileSystem: localFileSystem)
534+
} catch {
535+
self.observabilityScope.emit(warning: "Failed to load .netrc file at \(path). Error: \(error)")
536+
return nil
537+
}
538+
}
539+
540+
// Workspace's .netrc file should be consulted before user-global file
541+
// TODO: replace multiroot-data-file with explicit overrides
542+
if let localPath = try? (options.multirootPackageDataFile ?? self.getPackageRoot()).appending(component: ".netrc"),
543+
let localProvider = loadNetrcNoThrows(at: localPath) {
544+
providers.append(localProvider)
545+
}
524546

525-
// TODO: replace multiroot-data-file with explicit overrides
526-
let localPath = try (options.multirootPackageDataFile ?? self.getPackageRoot()).appending(component: ".netrc")
527-
if localFileSystem.exists(localPath) {
528-
return localPath
547+
let userHomePath = localFileSystem.homeDirectory.appending(component: ".netrc")
548+
if let userHomeProvider = loadNetrcNoThrows(at: userHomePath) {
549+
providers.append(userHomeProvider)
550+
}
529551
}
530-
531-
let userHomePath = localFileSystem.homeDirectory.appending(component: ".netrc")
532-
return localFileSystem.exists(userHomePath) ? userHomePath : .none
552+
553+
return providers
533554
}
534555

535556
private func getSharedCacheDirectory() throws -> AbsolutePath? {

Sources/PackageCollections/API.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public protocol PackageCollectionsProtocol {
112112
/// - reference: The package reference
113113
/// - callback: The closure to invoke when result becomes available
114114
// deprecated 9/21
115-
@available(*, deprecated, message: "user getPackageMetadata(identity:) instead")
115+
@available(*, deprecated, message: "use getPackageMetadata(identity:) instead")
116116
func getPackageMetadata(
117117
_ reference: PackageReference,
118118
callback: @escaping (Result<PackageCollectionsModel.PackageMetadata, Error>) -> Void
@@ -129,7 +129,7 @@ public protocol PackageCollectionsProtocol {
129129
/// processed collection will be used.
130130
/// - callback: The closure to invoke when result becomes available
131131
// deprecated 9/21
132-
@available(*, deprecated, message: "user getPackageMetadata(identity:) instead")
132+
@available(*, deprecated, message: "use getPackageMetadata(identity:) instead")
133133
func getPackageMetadata(
134134
_ reference: PackageReference,
135135
collections: Set<PackageCollectionsModel.CollectionIdentifier>?,

Tests/CommandsTests/RunToolTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final class RunToolTests: XCTestCase {
3737
XCTAssert(stdout.contains("Swift Package Manager"), "got stdout:\n" + stdout)
3838
}
3939

40-
func testUnkownProductAndArgumentPassing() throws {
40+
func testUnknownProductAndArgumentPassing() throws {
4141
fixture(name: "Miscellaneous/EchoExecutable") { path in
4242

4343
let result = try SwiftPMProduct.SwiftRun.executeProcess(

Tests/CommandsTests/SwiftToolTests.swift

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,76 +8,108 @@
88
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
99
*/
1010

11-
import Basics
11+
@testable import Basics
1212
@testable import Commands
1313
import SPMTestSupport
1414
import TSCBasic
1515
import XCTest
1616

1717
final class SwiftToolTests: XCTestCase {
18-
func testNetrcLocations() throws {
18+
func testNetrcAuthorizationProviders() throws {
1919
fixture(name: "DependencyResolution/External/XCFramework") { packageRoot in
2020
let fs = localFileSystem
21+
22+
let localPath = packageRoot.appending(component: ".netrc")
23+
let userHomePath = fs.homeDirectory.appending(component: ".netrc")
2124

2225
// custom .netrc file
23-
2426
do {
2527
let customPath = fs.homeDirectory.appending(component: UUID().uuidString)
2628
try fs.writeFileContents(customPath) {
2729
"machine mymachine.labkey.org login [email protected] password custom"
2830
}
2931

30-
3132
let options = try SwiftToolOptions.parse(["--package-path", packageRoot.pathString, "--netrc-file", customPath.pathString])
3233
let tool = try SwiftTool(options: options)
33-
XCTAssertEqual(try tool.getNetrcConfigFile().map(resolveSymlinks), resolveSymlinks(customPath))
34+
35+
let netrcProviders = try tool.getNetrcAuthorizationProviders()
36+
XCTAssertEqual(netrcProviders.count, 1)
37+
XCTAssertEqual(netrcProviders.first.map { resolveSymlinks($0.path) }, resolveSymlinks(customPath))
38+
3439
let auth = try tool.getAuthorizationProvider()?.authentication(for: URL(string: "https://mymachine.labkey.org")!)
3540
XCTAssertEqual(auth?.user, "[email protected]")
3641
XCTAssertEqual(auth?.password, "custom")
3742

3843
// delete it
3944
try localFileSystem.removeFileTree(customPath)
40-
XCTAssertThrowsError(try tool.getNetrcConfigFile(), "error expected") { error in
45+
XCTAssertThrowsError(try tool.getNetrcAuthorizationProviders(), "error expected") { error in
4146
XCTAssertEqual(error as? StringError, StringError("Did not find .netrc file at \(customPath)."))
4247
}
4348
}
4449

4550
// local .netrc file
46-
4751
do {
48-
let localPath = packageRoot.appending(component: ".netrc")
52+
// make sure there isn't a user home one
53+
try localFileSystem.removeFileTree(userHomePath)
54+
4955
try fs.writeFileContents(localPath) {
5056
return "machine mymachine.labkey.org login [email protected] password local"
5157
}
5258

5359
let options = try SwiftToolOptions.parse(["--package-path", packageRoot.pathString])
5460
let tool = try SwiftTool(options: options)
61+
62+
let netrcProviders = try tool.getNetrcAuthorizationProviders()
63+
XCTAssertEqual(netrcProviders.count, 1)
64+
XCTAssertEqual(netrcProviders.first.map { resolveSymlinks($0.path) }, resolveSymlinks(localPath))
5565

56-
XCTAssertEqual(try tool.getNetrcConfigFile().map(resolveSymlinks), resolveSymlinks(localPath))
5766
let auth = try tool.getAuthorizationProvider()?.authentication(for: URL(string: "https://mymachine.labkey.org")!)
5867
XCTAssertEqual(auth?.user, "[email protected]")
5968
XCTAssertEqual(auth?.password, "local")
6069
}
6170

6271
// user .netrc file
63-
6472
do {
6573
// make sure there isn't a local one
66-
try localFileSystem.removeFileTree(packageRoot.appending(component: ".netrc"))
74+
try localFileSystem.removeFileTree(localPath)
6775

68-
let userHomePath = fs.homeDirectory.appending(component: ".netrc")
6976
try fs.writeFileContents(userHomePath) {
7077
return "machine mymachine.labkey.org login [email protected] password user"
7178
}
7279

7380
let options = try SwiftToolOptions.parse(["--package-path", packageRoot.pathString])
7481
let tool = try SwiftTool(options: options)
7582

76-
XCTAssertEqual(try tool.getNetrcConfigFile().map(resolveSymlinks), resolveSymlinks(userHomePath))
83+
let netrcProviders = try tool.getNetrcAuthorizationProviders()
84+
XCTAssertEqual(netrcProviders.count, 1)
85+
XCTAssertEqual(netrcProviders.first.map { resolveSymlinks($0.path) }, resolveSymlinks(userHomePath))
86+
7787
let auth = try tool.getAuthorizationProvider()?.authentication(for: URL(string: "https://mymachine.labkey.org")!)
7888
XCTAssertEqual(auth?.user, "[email protected]")
7989
XCTAssertEqual(auth?.password, "user")
8090
}
91+
92+
// both local and user .netrc file
93+
do {
94+
try fs.writeFileContents(localPath) {
95+
return "machine mymachine.labkey.org login [email protected] password local"
96+
}
97+
try fs.writeFileContents(userHomePath) {
98+
return "machine mymachine.labkey.org login [email protected] password user"
99+
}
100+
101+
let options = try SwiftToolOptions.parse(["--package-path", packageRoot.pathString])
102+
let tool = try SwiftTool(options: options)
103+
104+
let netrcProviders = try tool.getNetrcAuthorizationProviders()
105+
XCTAssertEqual(netrcProviders.count, 2)
106+
XCTAssertEqual(netrcProviders.map { resolveSymlinks($0.path) }, [localPath, userHomePath].map(resolveSymlinks))
107+
108+
// local before user .netrc file
109+
let auth = try tool.getAuthorizationProvider()?.authentication(for: URL(string: "https://mymachine.labkey.org")!)
110+
XCTAssertEqual(auth?.user, "[email protected]")
111+
XCTAssertEqual(auth?.password, "local")
112+
}
81113
}
82114
}
83115
}

0 commit comments

Comments
 (0)