diff --git a/NOTICE.txt b/NOTICE.txt index 89b03e202f..42f7d5cfd6 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -31,15 +31,6 @@ This product contains Swift NIO. --- -This product contains Swift NIO SSL. - - * LICENSE (Apache License 2.0): - * https://www.apache.org/licenses/LICENSE-2.0 - * HOMEPAGE: - * https://github.com/apple/swift-nio-ssl - ---- - This product contains Swift Crypto. * LICENSE (Apache License 2.0): diff --git a/Package.resolved b/Package.resolved index 8dfdc4dcf1..25c17d9388 100644 --- a/Package.resolved +++ b/Package.resolved @@ -72,15 +72,6 @@ "revision": "1d425b0851ffa2695d488cce1d68df2539f42500", "version": "2.31.2" } - }, - { - "package": "swift-nio-ssl", - "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", - "state": { - "branch": null, - "revision": "2e74773972bd6254c41ceeda827f229bccbf1c0f", - "version": "2.15.0" - } } ] }, diff --git a/Package.swift b/Package.swift index bf2b0200c9..8ee30d9ae8 100644 --- a/Package.swift +++ b/Package.swift @@ -60,7 +60,6 @@ let package = Package( name: "SwiftDocCUtilities", dependencies: [ "SwiftDocC", - .product(name: "NIOSSL", package: "swift-nio-ssl"), .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "ArgumentParser", package: "swift-argument-parser") ]), @@ -115,7 +114,6 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { // Building standalone, so fetch all dependencies remotely. package.dependencies += [ .package(url: "https://github.com/apple/swift-nio.git", .upToNextMinor(from: "2.31.2")), - .package(url: "https://github.com/apple/swift-nio-ssl.git", .upToNextMinor(from: "2.15.0")), .package(name: "swift-markdown", url: "https://github.com/apple/swift-markdown.git", .branch("main")), .package(name: "CLMDB", url: "https://github.com/apple/swift-lmdb.git", .branch("main")), .package(url: "https://github.com/apple/swift-argument-parser", .upToNextMinor(from: "1.0.1")), @@ -133,7 +131,6 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { // Building in the Swift.org CI system, so rely on local versions of dependencies. package.dependencies += [ .package(path: "../swift-nio"), - .package(path: "../swift-nio-ssl"), .package(path: "../swift-markdown"), .package(name: "CLMDB", path: "../swift-lmdb"), .package(path: "../swift-argument-parser"), diff --git a/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift b/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift index 99dcf461d6..1085c5204c 100644 --- a/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift +++ b/Sources/SwiftDocCUtilities/Action/Actions/PreviewAction.swift @@ -52,19 +52,12 @@ public final class PreviewAction: Action, RecreatingContext { var logHandle = LogHandle.standardOutput - let tlsCertificateKey: URL? - let tlsCertificateChain: URL? - let serverUsername: String? - let serverPassword: String? let port: Int var convertAction: ConvertAction public var setupContext: ((inout DocumentationContext) -> Void)? private var previewPaths: [String] = [] - private var runSecure: Bool { - return tlsCertificateKey != nil && tlsCertificateChain != nil - } // Use for testing to override binding to a system port var bindServerToSocketPath: String? @@ -80,15 +73,7 @@ public final class PreviewAction: Action, RecreatingContext { /// Creates a new preview action from the given parameters. /// - /// The `tlsCertificateKey`, `tlsCertificateChain`, `serverUsername`, and `serverPassword` - /// parameters are optional, but if you provide one, all four are expected. They are used by the preview server - /// to serve content on the local network over SSL. - /// /// - Parameters: - /// - tlsCertificateKey: The path to the TLS certificate key used by the preview server for SSL configuration. - /// - tlsCertificateChain: The path to the TLS certificate chain used by the preview server for SSL configuration. - /// - serverUsername: The username used by the preview server for HTTP authentication. - /// - serverPassword: The password used by the preview server for HTTP authentication. /// - port: The port number used by the preview server. /// - convertAction: The action used to convert the documentation bundle before preview. /// On macOS, this action will be reused to convert documentation each time the source is modified. @@ -98,8 +83,7 @@ public final class PreviewAction: Action, RecreatingContext { /// is performed. /// - Throws: If an error is encountered while initializing the documentation context. public init( - tlsCertificateKey: URL?, tlsCertificateChain: URL?, serverUsername: String?, - serverPassword: String?, port: Int, + port: Int, createConvertAction: @escaping () throws -> ConvertAction, workspace: DocumentationWorkspace = DocumentationWorkspace(), context: DocumentationContext? = nil, @@ -110,10 +94,6 @@ public final class PreviewAction: Action, RecreatingContext { } // Initialize the action context. - self.tlsCertificateKey = tlsCertificateKey - self.tlsCertificateChain = tlsCertificateChain - self.serverUsername = serverUsername - self.serverPassword = serverPassword self.port = port self.createConvertAction = createConvertAction self.convertAction = try createConvertAction() @@ -123,6 +103,24 @@ public final class PreviewAction: Action, RecreatingContext { self.context = try context ?? DocumentationContext(dataProvider: workspace, diagnosticEngine: engine) self.printHTMLTemplatePath = printTemplatePath } + + @available(*, deprecated, message: "TLS support has been removed.") + public convenience init( + tlsCertificateKey: URL?, tlsCertificateChain: URL?, serverUsername: String?, + serverPassword: String?, port: Int, + createConvertAction: @escaping () throws -> ConvertAction, + workspace: DocumentationWorkspace = DocumentationWorkspace(), + context: DocumentationContext? = nil, + printTemplatePath: Bool = true) throws + { + try self.init( + port: port, + createConvertAction: createConvertAction, + workspace: workspace, + context: context, + printTemplatePath: printTemplatePath + ) + } /// Converts a documentation bundle and starts a preview server to render the result of that conversion. /// @@ -164,20 +162,12 @@ public final class PreviewAction: Action, RecreatingContext { // Preview the output and monitor the source bundle for changes. do { print(String(repeating: "=", count: 40), to: &logHandle) - if runSecure, let serverUsername = serverUsername, let serverPassword = serverPassword { - print("Starting TLS-Enabled Web Server", to: &logHandle) - printPreviewAddresses(base: URL(string: "https://\(ProcessInfo.processInfo.hostName):\(port)")!) - print("\tUsername: \(serverUsername)", to: &logHandle) - print("\tPassword: \(serverPassword)", to: &logHandle) - - } else { - print("Starting Local Preview Server", to: &logHandle) - printPreviewAddresses(base: URL(string: "http://localhost:\(port)")!) - } + print("Starting Local Preview Server", to: &logHandle) + printPreviewAddresses(base: URL(string: "http://localhost:\(port)")!) print(String(repeating: "=", count: 40), to: &logHandle) let to: PreviewServer.Bind = bindServerToSocketPath.map { .socket(path: $0) } ?? .localhost(port: port) - servers[serverIdentifier] = try PreviewServer(contentURL: convertAction.targetDirectory, bindTo: to, username: serverUsername, password: serverPassword, tlsCertificateChainURL: tlsCertificateChain, tlsCertificateKeyURL: tlsCertificateKey, logHandle: &logHandle) + servers[serverIdentifier] = try PreviewServer(contentURL: convertAction.targetDirectory, bindTo: to, logHandle: &logHandle) // When the user stops docc - stop the preview server first before exiting. trapSignals() diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift index b5437fa53d..c672757e4e 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/ActionExtensions/PreviewAction+CommandInitialization.swift @@ -23,10 +23,6 @@ extension PreviewAction { { // Initialize the `PreviewAction` from the options provided by the `Preview` command try self.init( - tlsCertificateKey: previewOptions.externalConnectionOptions.tlsCertificateKeyURL, - tlsCertificateChain: previewOptions.externalConnectionOptions.tlsCertificateChainURL, - serverUsername: previewOptions.externalConnectionOptions.username, - serverPassword: previewOptions.externalConnectionOptions.password, port: previewOptions.port, createConvertAction: { try ConvertAction( diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/ArgumentValidation/CredentialArgumentValidator.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/ArgumentValidation/CredentialArgumentValidator.swift deleted file mode 100644 index c053909e5e..0000000000 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/ArgumentValidation/CredentialArgumentValidator.swift +++ /dev/null @@ -1,74 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import ArgumentParser -import Foundation - -/// Simplifies validation of credential-related string command-line arguments. -enum CredentialArgumentValidator { - /// If a non-optional string value is provided, validates that the string is an acceptable username. - /// - /// A valid username must contain only alphanumerics characters and be at least three characters long. - /// - /// ### Example Error Message - /// Invalid username provided via `[argumentDescription]`. Username must be at least three alphanumeric characters. - /// - /// - Parameter username: An optional string value to be validated. - /// - Parameter argumentDescription: A description of the command line argument or environment - /// variable this string was initialized from. - /// - /// - Throws: A `ValidationError` that includes the `argumentDescription`. - static func validateUsername(_ username: String?, forArgumentDescription argumentDescription: String) throws { - // Validation is only necesary if a non-optional value has been passed. - guard let username = username else { return } - - // Check that the there are least three characters and that there are no characters - // besides those that are within the alphanumeric character set. - guard username.count >= 3 - && username.rangeOfCharacter(from: CharacterSet.alphanumerics.inverted) == nil else { - throw ValidationError( - """ - Invalid username provided via \(argumentDescription). - Username must be at least three alphanumeric characters. - """) - } - } - - /// If a non-optional, string value is provided, validates that the string is an acceptable password. - /// - /// A valid password must be at least eight characters and include mix-cased letters and numbers. - /// - /// ### Example Error Message - /// Invalid password provided via `[argumentDescription]`. - /// Password must be at least eight characters and include mix-cased letters and numbers. - /// - /// - Parameter username: An optional string value to be validated. - /// - Parameter argumentDescription: A description of the command line argument or environment - /// variable this string was initialized from. - /// - /// - Throws: A `ValidationError` that includes the `argumentDescription`. - static func validatePassword(_ password: String?, forArgumentDescription argumentDescription: String) throws { - // Validation is only necesary if a non-optional value has been passed. - guard let password = password else { return } - - // Check that there are at least eight characters and that the string contains - // lowercase, uppercase, and decimal digit values. - guard password.count >= 8 - && password.rangeOfCharacter(from: .lowercaseLetters) != nil - && password.rangeOfCharacter(from: .uppercaseLetters) != nil - && password.rangeOfCharacter(from: .decimalDigits) != nil else { - throw ValidationError( - """ - Invalid password provided via \(argumentDescription). - Password must be at least eight characters and include mix-cased letters and numbers. - """) - } - } -} diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewExternalConnectionOptions.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewExternalConnectionOptions.swift deleted file mode 100644 index c9ee01c038..0000000000 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewExternalConnectionOptions.swift +++ /dev/null @@ -1,127 +0,0 @@ -/* - This source file is part of the Swift.org open source project - - Copyright (c) 2021 Apple Inc. and the Swift project authors - Licensed under Apache License v2.0 with Runtime Library Exception - - See https://swift.org/LICENSE.txt for license information - See https://swift.org/CONTRIBUTORS.txt for Swift project authors -*/ - -import ArgumentParser -import Foundation - -/// Resolves and validates ``username``, ``password``, ``tlsCertificateChainURL``, and ``tlsCertificateKeyURL`` -/// values that can be used when configuring the preview server for external connections. -/// -/// These values can be set via via environment variables. -public struct PreviewExternalConnectionOptions: ParsableArguments { - - public init() {} - - // MARK: - Constants - - /// The environment variable key that can be used to set the ``username`` property. - static let usernameKey = "DOCC_PREVIEW_USERNAME" - - /// The environment variable key that can be used to set the ``password`` property. - static let passwordKey = "DOCC_PREVIEW_PASSWORD" - - /// The environment variable key that can be used to set the ``tlsCertificateChain`` property. - static let certificateChainKey = "DOCC_TLS_CERTIFICATE_CHAIN" - - /// The environment variable key that can be used to set the ``tlsCertificateKey`` property. - static let certificateKeyKey = "DOCC_TLS_CERTIFICATE_KEY" - - // MARK: - Environment Variable Values - - /// The username to use when configuring the preview server for external connections - /// as provided by the environment variable `DOCC_PREVIEW_USERNAME`. - public var username: String? { - ProcessInfo.processInfo.environment[PreviewExternalConnectionOptions.usernameKey] - } - - /// The password to use when configuring the preview server for external connections - /// as provided by the envrionment variable `DOCC_PREVIEW_PASSWORD`. - public var password: String? { - ProcessInfo.processInfo.environment[PreviewExternalConnectionOptions.passwordKey] - } - - /// The path to the TLS certificate chain to use when configuring the preview server for external connections - /// as provided by the envrionment variable `DOCC_TLS_CERTIFICATE_CHAIN`. - public var tlsCertificateChainURL: URL? { - ProcessInfo.processInfo.environment[PreviewExternalConnectionOptions.certificateChainKey] - .map { URL(fileURLWithPath: $0) } - } - - /// The path to the TLS certificate key to use when configuring the preview server for external connections - /// as provided by the envrionment variable `DOCC_TLS_CERTIFICATE_KEY`. - public var tlsCertificateKeyURL: URL? { - ProcessInfo.processInfo.environment[PreviewExternalConnectionOptions.certificateKeyKey] - .map { URL(fileURLWithPath: $0) } - } - - // MARK: - Public Properties - - /// A Boolean value indicating whether any configuration has been provided to enable external connections - /// for the preview server. - /// - /// If this value is true, and the `validate()` function has been called, - /// the ``username``, ``password``, ``tlsCertificateChainURL``, and - /// ``tlsCertificateKeyURL`` properties are all guaranteed to have valid values. - public var externalConnectionsAreEnabled: Bool { - username != nil || password != nil || tlsCertificateChainURL != nil - || tlsCertificateKeyURL != nil - } - - // MARK: - Validation - - public mutating func validate() throws { - // If a username has been provided, validate it - try CredentialArgumentValidator.validateUsername(username, - forArgumentDescription: """ - '\(PreviewExternalConnectionOptions.usernameKey)' environment variable. - """) - - // If a password has been provided, validate it - try CredentialArgumentValidator.validatePassword(password, - forArgumentDescription: """ - '\(PreviewExternalConnectionOptions.passwordKey)' environment variable. - """) - - // If a certificate chain URL has been provided, validate it - try URLArgumentValidator.validateFileExists(tlsCertificateChainURL, - forArgumentDescription: """ - '\(PreviewExternalConnectionOptions.certificateChainKey)' environment variable. - """) - - // If a certificate key URL has been provided, validate it - try URLArgumentValidator.validateFileExists(tlsCertificateKeyURL, - forArgumentDescription: """ - '\(PreviewExternalConnectionOptions.certificateKeyKey)' environment variable. - """) - - // If external connections are enabled, confirm that all four required values are present - if externalConnectionsAreEnabled { - let requiredValues: [(value: Any?, description: String)] = [ - (username, "username"), - (password, "password"), - (tlsCertificateChainURL, "tls-certificate-chain-path"), - (tlsCertificateKeyURL, "tls-certificate-key-path"), - ] - - // Collect the descriptions of all required values that are missing - let missingValues = requiredValues.filter { $0.value == nil }.map { $0.description } - - // If any values are missing, throw an error that lists all missing values - guard missingValues.isEmpty else { - throw ValidationError( - """ - Missing values that are required to configure the preview server for external connections. - If a password, username, certificate chain path, or certificate key path are provided, docc expects all four to be present. - Missing: \(missingValues.joined(separator: ", ")). - """) - } - } - } -} diff --git a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift index 691da92d37..835804eae1 100644 --- a/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift +++ b/Sources/SwiftDocCUtilities/ArgumentParsing/Options/PreviewOptions.swift @@ -35,18 +35,6 @@ public struct PreviewOptions: ParsableArguments { valueName: "port-number")) public var port: Int = 8080 - /// The options used when configuring the preview server for external connections. - /// - /// This group of options is only considered valid if either none of - /// the ``PreviewExternalConnectionOptions/username``, ``PreviewExternalConnectionOptions/password``, - /// ``PreviewExternalConnectionOptions/tlsCertificateChainURL``, ``PreviewExternalConnectionOptions/tlsCertificateKeyURL`` - /// values were provided **or** if they all were. - /// - /// If the ``PreviewExternalConnectionOptions/externalConnectionsAreEnabled`` Boolean value - /// is true, then **all** four values were provided and validated. - @OptionGroup() - public var externalConnectionOptions: PreviewExternalConnectionOptions - public mutating func validate() throws { // Check that a valid port has been provided guard port > 1023 else { diff --git a/Sources/SwiftDocCUtilities/PreviewServer/PreviewHTTPHandler.swift b/Sources/SwiftDocCUtilities/PreviewServer/PreviewHTTPHandler.swift index 972697b06c..a496478f43 100644 --- a/Sources/SwiftDocCUtilities/PreviewServer/PreviewHTTPHandler.swift +++ b/Sources/SwiftDocCUtilities/PreviewServer/PreviewHTTPHandler.swift @@ -18,7 +18,6 @@ import NIOHTTP1 /// via a web server bound to a port on the local machine or a socket. /// /// ### Features -/// - HTTP Basic Authentication when serving content to local network over SSL /// - Serving static files from pre-defined assets directory paths /// - A catch-all default GET response handler for all non-asset requests /// - Ignores unknown requests @@ -54,17 +53,13 @@ final class PreviewHTTPHandler: ChannelInboundHandler { private var handlerFuture: EventLoopFuture? private let fileIO: NonBlockingFileIO - // When serving content on local network over SSL we authorize users via these credentials - private let credentials: (user: String, pass: String)? - /// - Parameters: /// - fileIO: Async file I/O. /// - rootURL: The root of the content directory to serve. /// - credentials: Optional user credentials to authorize incoming requests. - public init(fileIO: NonBlockingFileIO, rootURL: URL, credentials: (user: String, pass: String)? = nil) { + init(fileIO: NonBlockingFileIO, rootURL: URL) { self.rootURL = rootURL self.fileIO = fileIO - self.credentials = credentials } /// Handles incoming data on a channel. @@ -72,8 +67,6 @@ final class PreviewHTTPHandler: ChannelInboundHandler { /// When receiving a request's head this method prepares the correct handler /// for the requested resource. /// - /// When receiving a request's tail this method sends content back to the - /// client and if neccessary verifies user-provided credentials when serving content over SSL. /// - Parameters: /// - context: A channel context. /// - data: The current inbound request data. @@ -93,26 +86,6 @@ final class PreviewHTTPHandler: ChannelInboundHandler { state = .requestInProgress(requestHead: head, handler: handler.create(channelHandler: self)) case (.end, .requestInProgress(let head, let handler)): - // In case the handler was initialized with user credentials, - // do verify they match the sent "Authorization" header - if let credentials = credentials { - - // Verify the user sent credentials over via the HTTP headers - let authorizationHeaders = head.headers["Authorization"] - guard !authorizationHeaders.isEmpty else { - error(context: context, requestPart: requestPart, head: head, status: .unauthorized, headers: [("WWW-Authenticate", "Basic realm=\"Preview server\"")]) - return - } - - let credentialsToken = [credentials.user, credentials.pass].joined(separator: ":") - .data(using: .utf8)!.base64EncodedString() - - guard authorizationHeaders[0] == "Basic \(credentialsToken)" else { - error(context: context, requestPart: requestPart, head: head, status: .forbidden) - return - } - } - defer { // Complete the response to the client, reset ``state`` completeResponse(context, trailers: nil, promise: nil) diff --git a/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift b/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift index a8eba14212..ad31b97dcf 100644 --- a/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift +++ b/Sources/SwiftDocCUtilities/PreviewServer/PreviewServer.swift @@ -13,13 +13,11 @@ import SwiftDocC import NIO import NIOHTTP1 -import NIOSSL /// A preview server that delivers documentation from a directory on disk. /// /// Call ``start()`` to bind the server to the given localhost port or socket, and -/// respond to HTTP requests. To serve the preview over SSL on the local network, -/// provide a credentials chain when initializing the server. +/// respond to HTTP requests. /// /// ### Design /// The server responds to two types of requests and ignores all others: @@ -34,7 +32,7 @@ import NIOSSL /// backlog of more than 16 pending client connections. /// ## Topics /// ### Serving Documentation -/// - ``init(contentURL:bindTo:username:password:tlsCertificateChainURL:tlsCertificateKeyURL:logHandle:)`` +/// - ``init(contentURL:bindTo:logHandle:)`` /// - ``Bind`` /// - ``start(onReady:)`` /// - ``stop()`` @@ -69,10 +67,6 @@ final class PreviewServer { internal var channel: Channel! private let contentURL: URL - private let username: String? - private let password: String? - private let tlsCertificateChainURL: URL? - private let tlsCertificateKeyURL: URL? /// A list of server-bind destinations. public enum Bind: CustomStringConvertible { @@ -100,23 +94,11 @@ final class PreviewServer { /// Creates a new preview server with the given content directory, bind destination, and credentials. /// - /// If you want to serve content over SSL provide a `username`, `password`, `tlsCertificateChainURL`, and `tlsCertificateKeyURL`. - /// DocC requires you to provide the two certificates in order to create an encrypted communication channel, and - /// additionally a username and password to authenticate users when serving documentation over a local network. - /// - /// If you use a self-signed SSL certificate to serve content from a local machine, - /// web browsers might warn visitors that the connection is not secure. - /// - Note: When you start the preview server with SSL enabled on macOS, you will be required to approve - /// network access via the standard system dialogue. /// - Parameters: /// - contentURL: The root URL on disk from which to serve content. /// - bindTo: Bind destination such as a localhost port or a file socket. - /// - username: A username to require, if serving secure content. - /// - password: A password to require, if serving secure content. - /// - tlsCertificateChainURL: A certificate chain to use for SSL, if serving secure content. - /// - tlsCertificateKeyURL: A certificate key to use for SSL, if serving secure content. /// - logHandle: A file handle to write logs to. - init(contentURL: URL, bindTo: Bind, username: String?, password: String?, tlsCertificateChainURL: URL? = nil, tlsCertificateKeyURL: URL? = nil, logHandle: inout LogHandle) throws { + init(contentURL: URL, bindTo: Bind, logHandle: inout LogHandle) throws { var isDirectory = ObjCBool(booleanLiteral: false) let contentPathExists = FileManager.default.fileExists(atPath: contentURL.path, isDirectory: &isDirectory) guard contentPathExists && isDirectory.boolValue else { @@ -125,10 +107,6 @@ final class PreviewServer { self.contentURL = contentURL self.bindTo = bindTo - self.username = username - self.password = password - self.tlsCertificateChainURL = tlsCertificateChainURL - self.tlsCertificateKeyURL = tlsCertificateKeyURL self.logHandle = logHandle } @@ -138,28 +116,6 @@ final class PreviewServer { /// - Parameter onReady: A closure that's executed after the server is bound successfully /// to its destination but before it has started serving content. func start(onReady: (() -> Void)? = nil) throws { - // An optional SSL context if required - let sslContext: NIOSSL.NIOSSLContext? - - if let tlsCertificateChainURL = tlsCertificateChainURL, - let tlsCertificateKeyURL = tlsCertificateKeyURL { - - print("SSL certificate chain: \(tlsCertificateChainURL.path)", to: &logHandle) - - // Will throw if cannot parse the provided PEM file - let certificateChain = try NIOSSLCertificate.fromPEMFile(tlsCertificateChainURL.path) - .map(NIOSSLCertificateSource.certificate) - - sslContext = try NIOSSLContext( - configuration: TLSConfiguration.makeServerConfiguration( - certificateChain: certificateChain, - privateKey: .file(tlsCertificateKeyURL.path) - ) - ) - } else { - sslContext = nil - } - // Create a server bootstrap let fileIO = NonBlockingFileIO(threadPool: threadPool) bootstrap = ServerBootstrap(group: group) @@ -169,27 +125,11 @@ final class PreviewServer { // Enable SO_REUSEADDR for the server itself .serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1) - // Configure the channel handler - it either handles plain HTTP requests or HTTPS over SSL + // Configure the channel handler - it handles plain HTTP requests .childChannelInitializer { channel in - if let sslContext = sslContext { - let sslHandler = NIOSSLServerHandler(context: sslContext) - - var credentials: (user: String, pass: String)? - if let username = self.username, let password = self.password { - credentials = (username, password) - } - - // HTTPS pipeline - return channel.pipeline.addHandler(sslHandler).flatMap { - channel.pipeline.configureHTTPServerPipeline(withServerUpgrade: nil, withErrorHandling: true).flatMap { - channel.pipeline.addHandler(PreviewHTTPHandler(fileIO: fileIO, rootURL: self.contentURL, credentials: credentials)) - } - } - } else { - // HTTP pipeline - return channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true).flatMap { - channel.pipeline.addHandler(PreviewHTTPHandler(fileIO: fileIO, rootURL: self.contentURL)) - } + // HTTP pipeline + return channel.pipeline.configureHTTPServerPipeline(withErrorHandling: true).flatMap { + channel.pipeline.addHandler(PreviewHTTPHandler(fileIO: fileIO, rootURL: self.contentURL)) } } @@ -206,15 +146,7 @@ final class PreviewServer { // Bind to the given destination switch bindTo { case .localhost(let port): - if sslContext != nil { - // If SSL is enabled, we bind to address `0.0.0.0` explicitly which - // will cause docc to request access to incoming network connections - // and allow users to connect to the preview server with external devices. - channel = try bootstrap.bind(to: SocketAddress(ipAddress: "0.0.0.0", port: port)).wait() - } else { - // Otherwise we bind to `localhost` which will not trigger this request. - channel = try bootstrap.bind(host: "localhost", port: port).wait() - } + channel = try bootstrap.bind(host: "localhost", port: port).wait() case .socket(let path): channel = try bootstrap.bind(unixDomainSocketPath: path).wait() } diff --git a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift index 051a369d0d..5485e20854 100644 --- a/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/ArgumentParsing/PreviewSubcommandTests.swift @@ -96,174 +96,5 @@ class PreviewSubcommandTests: XCTestCase { testBundleURL.path, ])) } - - // Test secure preview with valid certificate path & key - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "F1zzBuzz", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertNoThrow(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview with invalid certificate path - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "F1zzBuzz", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, - testTLSCertificate.appendingPathComponent("invalidPath.pem").path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview with invalid key path - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "F1zzBuzz", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, - testTLSKey.appendingPathComponent("invalidPath.pem").path, 1) - - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview has a key if the certificate chain is provided - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "F1zzBuzz", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - unsetenv(PreviewExternalConnectionOptions.certificateKeyKey) - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview has a certificate chain if the key is provided - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "F1zzBuzz", 1) - unsetenv(PreviewExternalConnectionOptions.certificateChainKey) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview with provided valid username and password - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "F1zzBuzz", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertNoThrow(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview with a username that is too short - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "ed", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "F1zzBuzz", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview with a username that contains symbols - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar$", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "F1zzBuzz", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview with an invalid password: too short - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "fixx", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview with an invalid password: all lowercase - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "f1zzbuzz", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview with an invalid password: all uppercase - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "F1ZZBUZZ", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } - - // Test secure preview with an invalid password: all characters - do { - setenv(TemplateOption.environmentVariableKey, templateDir.path, 1) - - setenv(PreviewExternalConnectionOptions.usernameKey, "foobar", 1) - setenv(PreviewExternalConnectionOptions.passwordKey, "fIzZbUzZ", 1) - setenv(PreviewExternalConnectionOptions.certificateChainKey, testTLSCertificate.path, 1) - setenv(PreviewExternalConnectionOptions.certificateKeyKey, testTLSKey.path, 1) - - XCTAssertThrowsError(try Docc.Preview.parse([ - testBundleURL.path, - ])) - } } } diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift index ddaa504ec1..a34775b0e9 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewActionIntegrationTests.swift @@ -303,10 +303,6 @@ class PreviewActionIntegrationTests: XCTestCase { } guard let preview = try? PreviewAction( - tlsCertificateKey: nil, - tlsCertificateChain: nil, - serverUsername: nil, - serverPassword: nil, port: bindPort, createConvertAction: createConvertAction) else { XCTFail("Could not create preview action from parameters", file: file, line: line) @@ -374,10 +370,6 @@ class PreviewActionIntegrationTests: XCTestCase { } guard let preview = try? PreviewAction( - tlsCertificateKey: nil, - tlsCertificateChain: nil, - serverUsername: nil, - serverPassword: nil, port: 0, // Use port 0 to pick a random free port number createConvertAction: createConvertAction) else { XCTFail("Could not create preview action from parameters") @@ -452,10 +444,6 @@ class PreviewActionIntegrationTests: XCTestCase { } guard let preview = try? PreviewAction( - tlsCertificateKey: nil, - tlsCertificateChain: nil, - serverUsername: nil, - serverPassword: nil, port: 8080, // We ignore this value when we set the `bindServerToSocketPath` property below. createConvertAction: createConvertAction) else { XCTFail("Could not create preview action from parameters") diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift index ab06076abd..7eb50d35fc 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewHTTPHandlerTests.swift @@ -81,90 +81,4 @@ class PreviewHTTPHandlerTests: XCTestCase { XCTAssertEqual(response.body, "index") } } - - func testPreviewAuthHandler() throws { - let tempFolderURL = try createTempFolder(content: [ - TextFile(name: "index.html", utf8Content: "index"), - Folder(name: "css", content: [ - TextFile(name: "test.css", utf8Content: "css"), - ]) - ]) - - let channel = EmbeddedChannel() - defer { - // Close the test channel, ignore any leftovers - _ = try? channel.finish() - } - - let channelHandler = PreviewHTTPHandler(fileIO: fileIO, rootURL: tempFolderURL, credentials: (user: "user", pass: "pass")) - - let response = Response() - - XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPResponseEncoder()).wait()) - XCTAssertNoThrow(try channel.pipeline.addHandler(response).wait()) - XCTAssertNoThrow(try channel.pipeline.addHandler(channelHandler).wait()) - XCTAssertNoThrow(try channel.pipeline.addHandler(HTTPServerPipelineHandler()).wait()) - - XCTAssertNoThrow(try channel.connect(to: SocketAddress(ipAddress: "127.0.0.1", port: 1)).wait()) - - // Request page without credentials - do { - let request = makeRequestHead(uri: "/tutorials") - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.head(request))) - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.end(nil))) - - XCTAssertEqual(response.head?.status, .unauthorized) - XCTAssertEqual(response.body, "") - } - - // Request asset without credentials, e.g. verify we authorize before serving content - do { - let request = makeRequestHead(uri: "/css/test.css") - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.head(request))) - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.end(nil))) - - XCTAssertEqual(response.head?.status, .unauthorized) - XCTAssertEqual(response.body, "") - } - - // Request error without credentials, e.g. verify we authorize before error handler - do { - let request = makeRequestHead(uri: "/css/notfound.css") - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.head(request))) - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.end(nil))) - - XCTAssertEqual(response.head?.status, .unauthorized) - XCTAssertEqual(response.body, "") - } - - // Request with valid credentials - do { - let request = makeRequestHead(uri: "/tutorials", headers: [("Authorization", "Basic \("user:pass".data(using: .utf8)!.base64EncodedString())")]) - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.head(request))) - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.end(nil))) - - XCTAssertEqual(response.head?.status, .ok) - XCTAssertEqual(response.body, "index") - } - - // Request error with valid credentials - do { - let request = makeRequestHead(uri: "/css/notfound.css", headers: [("Authorization", "Basic \("user:pass".data(using: .utf8)!.base64EncodedString())")]) - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.head(request))) - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.end(nil))) - - XCTAssertEqual(response.head?.status, .notFound) - XCTAssertEqual(response.body, "") - } - - // Request with invalid credentials - do { - let request = makeRequestHead(uri: "/tutorials", headers: [("Authorization", "Basic \("USER:PASS".data(using: .utf8)!.base64EncodedString())")]) - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.head(request))) - XCTAssertNoThrow(try channel.writeInbound(HTTPServerRequestPart.end(nil))) - - XCTAssertEqual(response.head?.status, .forbidden) - XCTAssertEqual(response.body, "") - } - } } diff --git a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewServerTests.swift b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewServerTests.swift index 5403dfedd4..93bd36d842 100644 --- a/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewServerTests.swift +++ b/Tests/SwiftDocCUtilitiesTests/PreviewServer/PreviewServerTests.swift @@ -46,7 +46,7 @@ class PreviewServerTests { // Run test server var log = LogHandle.none - let server = try PreviewServer(contentURL: tempFolderURL, bindTo: .socket(path: socketURL.path), username: "username", password: "password", logHandle: &log) + let server = try PreviewServer(contentURL: tempFolderURL, bindTo: .socket(path: socketURL.path), logHandle: &log) // Assert server starts let expectationStarted = AsynchronousExpectation(description: "Server before start") @@ -126,7 +126,7 @@ class PreviewServerTests { // Create the server var log = LogHandle.none - let server = try PreviewServer(contentURL: tempFolderURL, bindTo: .socket(path: socketURL.path), username: "username", password: "password", logHandle: &log) + let server = try PreviewServer(contentURL: tempFolderURL, bindTo: .socket(path: socketURL.path), logHandle: &log) // Start the server let expectationStarted = AsynchronousExpectation(description: "Server before start") @@ -173,7 +173,7 @@ class PreviewServerTests { // Create the server var log = LogHandle.none - let server = try PreviewServer(contentURL: tempFolderURL, bindTo: .socket(path: socketURL.path), username: "username", password: "password", logHandle: &log) + let server = try PreviewServer(contentURL: tempFolderURL, bindTo: .socket(path: socketURL.path), logHandle: &log) // Start the server let expectationStarted = AsynchronousExpectation(description: "Server before start")