Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Sources/tart/OCI/WWWAuthenticate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ class WWWAuthenticate {
+ "key=value or key=\"value\"")
}

let key = String(parts[0])
var value = String(parts[1])
// Directives can be surrounded by optional whitespace[1]
//
// [1]: https://www.rfc-editor.org/rfc/rfc7235#section-4.1
let key = parts[0].trimmingCharacters(in: .whitespaces)
var value = parts[1].trimmingCharacters(in: .whitespaces)
value = value.trimmingCharacters(in: CharacterSet(charactersIn: "\""))

kvs[key] = value
Expand Down
11 changes: 8 additions & 3 deletions Tests/TartTests/RegistryRealmTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ final class RegistryRealmTests: XCTestCase {
try await checkAuthentication(mode: "encoded")
}

func testChallengeWithSpacesAfterCommas() async throws {
try await checkAuthentication(mode: "spaced")
}

private func checkAuthentication(mode: String) async throws {
let server = try RealmTokenServer(mode: mode)
defer { server.stop() }
Expand Down Expand Up @@ -83,15 +87,16 @@ private final class RealmTokenServer {
if !request.lowercased().contains("authorization: bearer test-token\r\n") {
let query: String
switch self.mode {
case "plain": query = ""
case "plain", "spaced": query = ""
case "encoded": query = "?tenant=alpha%2Bbeta&escape=%7e%2f&mode=one&mode=two&empty="
default: query = "?tenant=alpha%26beta&mode=one&mode=two&empty="
}
let stale = self.mode == "replace" ? "&scope=old&service=old" :
(self.mode == "encoded" ? "&sc%6Fpe=old&service=old" : "")
let realm = "http://127.0.0.1:\(self.listener.port!.rawValue)/token\(query)\(stale)"
status = "401 Unauthorized"
extraHeaders = "WWW-Authenticate: Bearer realm=\"\(realm)\",service=\"registry\",scope=\"repository:org/repo:pull\"\r\n"
let separator = self.mode == "spaced" ? ", " : ","
extraHeaders = "WWW-Authenticate: Bearer realm=\"\(realm)\"\(separator)service=\"registry\"\(separator)scope=\"repository:org/repo:pull\"\r\n"
}
} else if url.path == "/token" {
let receivedURL = URLComponents(url: url, resolvingAgainstBaseURL: false)!
Expand All @@ -102,7 +107,7 @@ private final class RealmTokenServer {
values("tenant") == ["alpha+beta"] && values("escape") == ["~/"]
let normalValid = values("tenant") == ["alpha&beta"]
let valid = values("service") == ["registry"] && values("scope") == ["repository:org/repo:pull"] &&
(self.mode == "plain" || ((self.mode == "encoded" ? encodedValid : normalValid) &&
(self.mode == "plain" || self.mode == "spaced" || ((self.mode == "encoded" ? encodedValid : normalValid) &&
values("mode") == ["one", "two"] && values("empty") == [""]))
status = valid ? "200 OK" : "400 Bad Request"
body = valid ? "{\"token\":\"test-token\"}" : "{\"error\":\"missing or changed realm parameters\"}"
Expand Down
14 changes: 14 additions & 0 deletions Tests/TartTests/WWWAuthenticateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ final class WWWAuthenticateTests: XCTestCase {
XCTAssertEqual(["a": "b", "c": "d"], wwwAuthenticate.kvs)
}

func testWhitespaceAroundDirectives() throws {
// RFC 7235 allows optional whitespace around the commas separating directives[1]
//
// [1]: https://www.rfc-editor.org/rfc/rfc7235#section-4.1
let wwwAuthenticate = try WWWAuthenticate(rawHeaderValue: "Bearer realm=\"https://auth.example.com/token\", service=\"registry.example.com\" , scope=\"repository:org/repo:pull,push\"")

XCTAssertEqual("Bearer", wwwAuthenticate.scheme)
XCTAssertEqual([
"realm": "https://auth.example.com/token",
"service": "registry.example.com",
"scope": "repository:org/repo:pull,push",
], wwwAuthenticate.kvs)
}

func testIncompleteHeader() throws {
XCTAssertThrowsError(try WWWAuthenticate(rawHeaderValue: "Whatever")) {
XCTAssertTrue($0 is RegistryError)
Expand Down