Skip to content

Commit d3e4543

Browse files
committed
Update auth logic
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 522d8c7 commit d3e4543

File tree

6 files changed

+57
-20
lines changed

6 files changed

+57
-20
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: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,15 @@ public struct SwiftToolOptions: ParsableArguments {
328328
help: "Specify the .netrc file path.",
329329
completion: .file())
330330
var netrcFilePath: AbsolutePath?
331+
332+
#if canImport(Security)
333+
/// Whether to use keychain for authenticating with remote servers
334+
/// when downloading binary artifacts or communicating with a registry.
335+
@Flag(inversion: .prefixedNo,
336+
exclusivity: .exclusive,
337+
help: "Search credentials in OS keychain")
338+
var keychain: Bool = true
339+
#endif
331340

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

Sources/Commands/SwiftTool.swift

Lines changed: 39 additions & 16 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,35 +497,58 @@ public class SwiftTool {
497497

498498
func getAuthorizationProvider() throws -> AuthorizationProvider? {
499499
var providers = [AuthorizationProvider]()
500-
// netrc file has higher specificity than keychain so use it first
501-
if let workspaceNetrc = try self.getNetrcConfig()?.get() {
502-
providers.append(workspaceNetrc)
503-
}
504500

505-
// TODO: add --no-keychain option to allow opt-out
506-
//#if canImport(Security)
507-
// providers.append(KeychainAuthorizationProvider(observabilityScope: self.observabilityScope))
508-
//#endif
501+
// netrc has higher specificity than keychain so use it first
502+
try providers.append(contentsOf: self.getNetrcAuthorizationProviders())
503+
504+
#if canImport(Security)
505+
if self.options.keychain {
506+
providers.append(KeychainAuthorizationProvider(observabilityScope: self.observabilityScope))
507+
}
508+
#endif
509509

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

513-
func getNetrcConfig() -> Workspace.Configuration.Netrc? {
514-
guard options.netrc else { return nil }
513+
func getNetrcAuthorizationProviders() throws -> [NetrcAuthorizationProvider] {
514+
guard options.netrc else { return [] }
515515

516+
var providers = [NetrcAuthorizationProvider]()
517+
518+
// Use custom .netrc file if specified, otherwise look for it within workspace and user's home directory.
516519
if let configuredPath = options.netrcFilePath {
517520
guard localFileSystem.exists(configuredPath) else {
518521
self.observabilityScope.emit(error: "Did not find .netrc file at \(configuredPath).")
519-
return nil
522+
return providers
520523
}
521524

522-
return .init(path: configuredPath, fileSystem: localFileSystem)
525+
providers.append(try NetrcAuthorizationProvider(path: configuredPath, fileSystem: localFileSystem))
523526
} else {
524-
let defaultPath = localFileSystem.homeDirectory.appending(component: ".netrc")
525-
guard localFileSystem.exists(defaultPath) else { return nil }
527+
// User didn't tell us to use these .netrc files so be more lenient with errors
528+
func loadNetrcNoThrows(at path: AbsolutePath) -> NetrcAuthorizationProvider? {
529+
guard localFileSystem.exists(path) else { return nil }
530+
531+
do {
532+
return try NetrcAuthorizationProvider(path: path, fileSystem: localFileSystem)
533+
} catch {
534+
self.observabilityScope.emit(warning: "Failed to load .netrc file at \(path). Error: \(error)")
535+
return nil
536+
}
537+
}
538+
539+
// Workspace's .netrc file should be consulted before user-global file
540+
if let workspaceNetrcPath = try? Workspace.DefaultLocations.netrcFile(forRootPackage: self.getPackageRoot()),
541+
let workspaceNetrcProvider = loadNetrcNoThrows(at: workspaceNetrcPath) {
542+
providers.append(workspaceNetrcProvider)
543+
}
526544

527-
return .init(path: defaultPath, fileSystem: localFileSystem)
545+
let userNetrcPath = localFileSystem.homeDirectory.appending(component: ".netrc")
546+
if let userNetrcProvider = loadNetrcNoThrows(at: userNetrcPath) {
547+
providers.append(userNetrcProvider)
548+
}
528549
}
550+
551+
return providers
529552
}
530553

531554
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>?,

Sources/Workspace/WorkspaceConfiguration.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ extension Workspace {
156156
public static func registriesConfigurationFile(at path: AbsolutePath) -> AbsolutePath {
157157
path.appending(component: "registries.json")
158158
}
159+
160+
public static func netrcFile(forRootPackage rootPath: AbsolutePath) -> AbsolutePath {
161+
rootPath.appending(component: ".netrc")
162+
}
159163

160164
public static func manifestsDirectory(at path: AbsolutePath) -> AbsolutePath {
161165
path.appending(component: "manifests")
@@ -346,6 +350,7 @@ extension Workspace.Configuration {
346350
// MARK: - Authentication
347351

348352
extension Workspace.Configuration {
353+
@available(*, deprecated, message: "Use NetrcAuthorizationProvider instead")
349354
public struct Netrc {
350355
private let path: AbsolutePath
351356
private let fileSystem: FileSystem

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(

0 commit comments

Comments
 (0)