Skip to content

Commit

Permalink
Merge pull request #112 from niscy-eudiw/bugfix/dpop-header-parsing
Browse files Browse the repository at this point in the history
Request header case insensitive parsing
  • Loading branch information
dtsiflit authored Feb 25, 2025
2 parents baa5b85 + df03510 commit 2eedaa7
Show file tree
Hide file tree
Showing 7 changed files with 30 additions and 8 deletions.
6 changes: 6 additions & 0 deletions Sources/Extensions/Dictionary+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,9 @@ public extension Dictionary where Key == String, Value == Any {
return keySet.isSubset(of: dictionaryKeySet)
}
}

extension Dictionary where Key == AnyHashable {
func value(forCaseInsensitiveKey key: String) -> Value? {
return self.first { ($0.key as? String)?.caseInsensitiveCompare(key) == .orderedSame }?.value
}
}
5 changes: 3 additions & 2 deletions Sources/Extensions/HTTPURLResponse+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ public extension HTTPURLResponse {

func containsDpopError() -> Bool {
guard statusCode == HTTPStatusCode.unauthorized,
let wwwAuth = valueForHeader("WWW-Authenticate") else {
let wwwAuth = valueForHeader("www-authenticate") else {
return false
}
return wwwAuth.contains("DPoP") && wwwAuth.contains("error=\"use_dpop_nonce\"")
return wwwAuth.containsCaseInsensitive("DPoP") &&
wwwAuth.containsCaseInsensitive("error=\"use_dpop_nonce\"")
}
}

15 changes: 15 additions & 0 deletions Sources/Extensions/String+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,21 @@ import CryptoKit

public extension String {

/// Checks if the string contains another string in a case-insensitive manner.
///
/// - Parameter other: The substring to search for.
/// - Returns: `true` if the string contains `other`, ignoring case; otherwise, `false`.
///
/// ### Example Usage:
/// ```swift
/// let text = "Hello, Swift Programming!"
/// print(text.containsCaseInsensitive("swift")) // true
/// print(text.containsCaseInsensitive("python")) // false
/// ```
func containsCaseInsensitive(_ other: String) -> Bool {
return self.range(of: other, options: .caseInsensitive) != nil
}

/// Generates a random Base64URL-encoded string of the specified length.
///
/// - Parameter length: The length of the random string to generate.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Main/Authorisers/AuthorizationServerClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let OPENID_CREDENTIAL = "openid_credential"

private extension ResponseWithHeaders {
func dpopNonce() -> Nonce? {
if let nonceValue = headers[Constants.DPOP_NONCE_HEADER] as? String {
if let nonceValue = headers.value(forCaseInsensitiveKey: Constants.DPOP_NONCE_HEADER) as? String {
return Nonce(value: nonceValue)
}
return nil
Expand Down
2 changes: 1 addition & 1 deletion Sources/Utilities/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public struct Constants {

public static let url = "https://a.bc"

public static let DPOP_NONCE_HEADER = "DPoP-Nonce"
public static let DPOP_NONCE_HEADER = "dpop-nonce"
public static let USE_DPOP_NONCE = "use_dpop_nonce"

public static let REFRESH_TOKEN = "refresh_token"
Expand Down
4 changes: 2 additions & 2 deletions Sources/Utilities/RemoteDataAccess/Poster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public struct Poster: PostingType {
if statusCode >= HTTPStatusCode.badRequest && statusCode < HTTPStatusCode.internalServerError {
if let httpResponse,
httpResponse.containsDpopError(),
let dPopNonce = headers[Constants.DPOP_NONCE_HEADER] as? String {
let dPopNonce = headers.value(forCaseInsensitiveKey: Constants.DPOP_NONCE_HEADER) as? String {
return .failure(
.useDpopNonce(
.init(
Expand All @@ -130,7 +130,7 @@ public struct Poster: PostingType {
} else {
let object = try JSONDecoder().decode(GenericErrorResponse.self, from: data)
if object.error == Constants.USE_DPOP_NONCE,
let dPopNonce = headers[Constants.DPOP_NONCE_HEADER] as? String {
let dPopNonce = headers.value(forCaseInsensitiveKey: Constants.DPOP_NONCE_HEADER) as? String {
return .failure(
.useDpopNonce(
.init(
Expand Down
4 changes: 2 additions & 2 deletions Tests/Constants/TestsConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import JOSESwift

@testable import OpenID4VCI

let CREDENTIAL_ISSUER_PUBLIC_URL = "https://dev.issuer-backend.eudiw.dev"
let CREDENTIAL_ISSUER_PUBLIC_URL = "https://issuer-backend.eudiw.dev"
let MDL_config_id = "org.iso.18013.5.1.mDL"
let PID_MsoMdoc_config_id = "eu.europa.ec.eudi.pid_mso_mdoc"
let PID_SdJwtVC_config_id = "eu.europa.ec.eudi.pid_vc_sd_jwt"
Expand All @@ -34,7 +34,7 @@ let PID_SdJwtVC_config_id = "eu.europa.ec.eudi.pid_vc_sd_jwt"
//let PID_mDL_SCOPE = "eu.europa.ec.eudi.mdl_mdoc"

let CREDENTIAL_OFFER_QR_CODE_URL = """
eudi-openid4ci://credentialsOffer?credential_offer=%7B%22credential_issuer%22:%22https://dev.issuer-backend.eudiw.dev%22,%22credential_configuration_ids%22:[%22eu.europa.ec.eudi.pid_mso_mdoc%22,%22eu.europa.ec.eudi.pid_vc_sd_jwt%22,%22org.iso.18013.5.1.mDL%22],%22grants%22:%7B%22authorization_code%22:%7B%22authorization_server%22:%22https://dev.auth.eudiw.dev/realms/pid-issuer-realm%22%7D%7D%7D
eudi-openid4ci://credentialsOffer?credential_offer=%7B%22credential_issuer%22:%22https://issuer-backend.eudiw.dev%22,%22credential_configuration_ids%22:[%22eu.europa.ec.eudi.pid_mso_mdoc%22,%22eu.europa.ec.eudi.pid_vc_sd_jwt%22,%22org.iso.18013.5.1.mDL%22],%22grants%22:%7B%22authorization_code%22:%7B%22authorization_server%22:%22https://dev.auth.eudiw.dev/realms/pid-issuer-realm%22%7D%7D%7D
"""

let All_Supported_CredentialOffer = """
Expand Down

0 comments on commit 2eedaa7

Please sign in to comment.