117 lines
4.5 KiB
Swift
117 lines
4.5 KiB
Swift
//
|
|
// ClientAndAuthTests.swift
|
|
// PortalKitTests
|
|
//
|
|
// Verification of CredentialStorage abstractions, PortalAuth, and PortalClient models.
|
|
//
|
|
|
|
import XCTest
|
|
@testable import PortalKit
|
|
|
|
final class ClientAndAuthTests: XCTestCase {
|
|
func testInMemoryCredentialStorage() {
|
|
let storage = InMemoryCredentialStorage()
|
|
XCTAssertNil(storage.getAuthToken())
|
|
XCTAssertNil(storage.getPinnedCertSha256())
|
|
|
|
storage.save(token: "test_token_123", certSha256: "AABBCCDDEEFF")
|
|
XCTAssertEqual(storage.getAuthToken(), "test_token_123")
|
|
XCTAssertEqual(storage.getPinnedCertSha256(), "aabbccddeeff") // lowercased
|
|
|
|
storage.clear()
|
|
XCTAssertNil(storage.getAuthToken())
|
|
XCTAssertNil(storage.getPinnedCertSha256())
|
|
}
|
|
|
|
func testFileCredentialStorage() {
|
|
let tempDir = FileManager.default.temporaryDirectory
|
|
let tempFile = tempDir.appendingPathComponent("test_credentials_\(UUID().uuidString).json")
|
|
defer { try? FileManager.default.removeItem(at: tempFile) }
|
|
|
|
let storage = FileCredentialStorage(fileURL: tempFile)
|
|
XCTAssertNil(storage.getAuthToken())
|
|
|
|
storage.save(token: "tok_abc", certSha256: "010203040506")
|
|
XCTAssertEqual(storage.getAuthToken(), "tok_abc")
|
|
XCTAssertEqual(storage.getPinnedCertSha256(), "010203040506")
|
|
|
|
// Create fresh instance pointing to same file
|
|
let reloadStorage = FileCredentialStorage(fileURL: tempFile)
|
|
XCTAssertEqual(reloadStorage.getAuthToken(), "tok_abc")
|
|
XCTAssertEqual(reloadStorage.getPinnedCertSha256(), "010203040506")
|
|
|
|
reloadStorage.clear()
|
|
XCTAssertNil(reloadStorage.getAuthToken())
|
|
}
|
|
|
|
func testPortalAuthFacade() {
|
|
let original = PortalAuth.defaultStorage
|
|
defer { PortalAuth.defaultStorage = original }
|
|
|
|
let mockStorage = InMemoryCredentialStorage()
|
|
PortalAuth.defaultStorage = mockStorage
|
|
|
|
PortalAuth.save(token: "facade_token", certSha256: "CAFEBABE")
|
|
XCTAssertEqual(PortalAuth.token, "facade_token")
|
|
XCTAssertEqual(PortalAuth.pinnedCertSha256, "cafebabe")
|
|
|
|
PortalAuth.clear()
|
|
XCTAssertNil(PortalAuth.token)
|
|
XCTAssertNil(PortalAuth.pinnedCertSha256)
|
|
}
|
|
|
|
func testPortalClientHostNormalization() {
|
|
let c1 = PortalClient(host: "10.0.0.10")
|
|
XCTAssertEqual(c1.host, "10.0.0.10:\(PortalEndpoints.port)")
|
|
|
|
let c2 = PortalClient(host: "https://10.0.0.10:9000/")
|
|
XCTAssertEqual(c2.host, "10.0.0.10:9000")
|
|
|
|
let c3 = PortalClient(host: "http://myportal.local:\(PortalEndpoints.port)")
|
|
XCTAssertEqual(c3.host, "myportal.local:\(PortalEndpoints.port)")
|
|
}
|
|
|
|
func testPortalClientControlRequiresPairing() async {
|
|
let storage = InMemoryCredentialStorage()
|
|
let client = PortalClient(host: "127.0.0.1", credentialStorage: storage)
|
|
|
|
do {
|
|
_ = try await client.control(command: "mode Desk")
|
|
XCTFail("Should throw notPaired error")
|
|
} catch {
|
|
guard case PortalClientError.notPaired = error else {
|
|
XCTFail("Expected notPaired error, got \(error)")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func testTlsPinningChallengeEvaluationNonServerTrust() {
|
|
let space = URLProtectionSpace(
|
|
host: "localhost",
|
|
port: PortalEndpoints.port,
|
|
protocol: "https",
|
|
realm: nil,
|
|
authenticationMethod: NSURLAuthenticationMethodHTTPBasic
|
|
)
|
|
let challenge = URLAuthenticationChallenge(protectionSpace: space, proposedCredential: nil, previousFailureCount: 0, failureResponse: nil, error: nil, sender: DummyChallengeSender())
|
|
|
|
let exp = expectation(description: "Challenge evaluated")
|
|
PortalTlsPinning.evaluate(challenge: challenge, pinnedFingerprint: "somehash") { disposition, credential in
|
|
XCTAssertEqual(disposition, .cancelAuthenticationChallenge)
|
|
XCTAssertNil(credential)
|
|
exp.fulfill()
|
|
}
|
|
wait(for: [exp], timeout: 1)
|
|
}
|
|
}
|
|
|
|
private final class DummyChallengeSender: NSObject, URLAuthenticationChallengeSender {
|
|
func use(_ credential: URLCredential, for challenge: URLAuthenticationChallenge) {}
|
|
func continueWithoutCredential(for challenge: URLAuthenticationChallenge) {}
|
|
func cancel(_ challenge: URLAuthenticationChallenge) {}
|
|
func performDefaultHandling(for challenge: URLAuthenticationChallenge) {}
|
|
func rejectProtectionSpaceAndContinue(with challenge: URLAuthenticationChallenge) {}
|
|
}
|
|
|