diff --git a/AgileWorks/AgileWorks/App/RootViewController.swift b/AgileWorks/AgileWorks/App/RootViewController.swift index 77102984917768fce52688884dcd6fe0583b9988..e6712b7da8250220b84d9b07b8991560fe09cdc6 100644 --- a/AgileWorks/AgileWorks/App/RootViewController.swift +++ b/AgileWorks/AgileWorks/App/RootViewController.swift @@ -109,6 +109,7 @@ extension RootViewController { KeychainDataStore().removeAccessToken(serverNumber: serverNumber) KeychainDataStore().removeDeviceID(serverNumber: serverNumber) KeychainDataStore().removeSessionID(serverNumber: serverNumber) + KeychainDataStore().removeCookieHeader(serverNumber: serverNumber) } //認証情報削除 KeychainDataStore().removeSystemName() diff --git a/AgileWorks/AgileWorks/WebView/View/WebViewController.swift b/AgileWorks/AgileWorks/WebView/View/WebViewController.swift index edee03256efde2a90d0b9553e141194d43101d2d..106cc37ea7317fde2065be0e5b3fef6e9855035a 100644 --- a/AgileWorks/AgileWorks/WebView/View/WebViewController.swift +++ b/AgileWorks/AgileWorks/WebView/View/WebViewController.swift @@ -166,12 +166,22 @@ class WebViewController: UIViewController { // WebView ロード処理 private func loadWebView(url: String) { - let sesstionid = "JSESSIONID=" + KeychainDataStore().readSessionID()! - + var requestCookie = "" + if let cookieHeader = KeychainDataStore().readCookieHeader(){ + let individualCookies = self.separateCookieHeader(cookieHeader: cookieHeader) + for cookie in individualCookies { + let componentCookies = cookie.components(separatedBy: ";") + for componentCookie in componentCookies { + if !(componentCookie.contains("Path=") || componentCookie.contains("Secure") || componentCookie.contains("HttpOnly") || componentCookie.contains("Expires=") || componentCookie.contains("Max-Age=") || componentCookie.contains("Domain=")) { + requestCookie += componentCookie + ";" + } + } + } + } let url = URL(string: url)! var request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 30.0) request.httpShouldHandleCookies = false - request.setValue(sesstionid, forHTTPHeaderField: "Cookie") + request.setValue(requestCookie, forHTTPHeaderField: "Cookie") log.debug(""" Session Request: \(String(describing: request.url)) \nHTTPMethod: \(String(describing: request.httpMethod) ) @@ -188,6 +198,22 @@ class WebViewController: UIViewController { self.setWebView() loadWebView(url: url) } + + private func separateCookieHeader(cookieHeader: String) -> [String] { + var individualCookies: [String] = [] + let cookies = cookieHeader.components(separatedBy: ",") + var currentCookie = "" + for cookie in cookies { + currentCookie += cookie + if cookie.contains("Path=") { + individualCookies.append(currentCookie) + currentCookie = "" + } else { + currentCookie += "," + } + } + return individualCookies + } // sessionId を cookie にセット private func setSessionId() { @@ -201,6 +227,61 @@ class WebViewController: UIViewController { ]) let cookieStore = self.mainWebView.configuration.websiteDataStore.httpCookieStore cookieStore.setCookie(cookie!) + + if let cookieHeader = KeychainDataStore().readCookieHeader(){ + let individualCookies = self.separateCookieHeader(cookieHeader: cookieHeader) + for cookie in individualCookies { + var cookiePath = "" + var cookieName = "" + var cookieValue = "" + var samesite: String? = "" + var secure = false + let components = cookie.components(separatedBy: ";") + for component in components { + if component.contains("Path=") { + let componentValue = component.split(separator: "=") + if componentValue.count == 1 { + cookiePath = String(componentValue[1]) + } + } else if component.contains("Secure") { + secure = true + } else if component.contains("SameSite=") { + let componentValue = component.split(separator: "=") + if componentValue.count == 1 { + if String(componentValue[1]) == "Strict" { + samesite = HTTPCookieStringPolicy.sameSiteStrict.rawValue + } else if String(componentValue[1]) == "Lax" { + samesite = HTTPCookieStringPolicy.sameSiteLax.rawValue + } else if String(componentValue[1]) == "None" { + samesite = nil + } + } + } else if !(component.contains("HttpOnly") || component.contains("Expires=") || component.contains("Max-Age=") || component.contains("Domain=")) { + let componentValue = component.split(separator: "=") + if componentValue.count == 2 { + cookieName = String(componentValue[0]) + cookieValue = String(componentValue[1]) + } + } + } + if samesite == nil { + if secure != true { + samesite = "" + } + } + if cookieName != "JSESSIONID" { + let cookie = HTTPCookie(properties: [ + HTTPCookiePropertyKey.domain: KeychainDataStore().readServerURL() ?? "", + HTTPCookiePropertyKey.path: cookiePath, + HTTPCookiePropertyKey.secure: secure, + HTTPCookiePropertyKey.name: cookieName, + HTTPCookiePropertyKey.value: cookieValue, + HTTPCookiePropertyKey.sameSitePolicy: samesite + ]) + cookieStore.setCookie(cookie!) + } + } + } } } diff --git a/AgileWorks/Common/DataStore/KeychainDataStore.swift b/AgileWorks/Common/DataStore/KeychainDataStore.swift index 03de57d9aad1e30614ffe427c83b906ed362b5b2..d6de3c24a6c824e5e701e8ff7bb76abf0014d049 100644 --- a/AgileWorks/Common/DataStore/KeychainDataStore.swift +++ b/AgileWorks/Common/DataStore/KeychainDataStore.swift @@ -21,6 +21,7 @@ final class KeychainDataStore: DataStoreProtocol { private let kSessionID: String = "SessionID" private let kSystemName: String = "SystemName" private let kCertificateLabel: String = "CertificateLabel" + private let kCookieHeader: String = "CookieHeader" // アクセストークンの書き込み func writeAccessToken(accessToken: String) { @@ -175,6 +176,25 @@ final class KeychainDataStore: DataStoreProtocol { removeKeychainValue(key: kSessionID) } } + + // cookieの書き込み + func writeCookieHeader(cookieHeader: String) { + setKeychainValue(key: kCookieHeader, value: cookieHeader) + } + + // cookieの読み込み + func readCookieHeader() -> String? { + return getKeychainValue(key: kCookieHeader) + } + + // cookieの削除 + func removeCookieHeader(serverNumber: Int? = nil) { + if let serverNumber = serverNumber { + removeKeychainValue(key: kCookieHeader, serverNumber: serverNumber) + } else { + removeKeychainValue(key: kCookieHeader) + } + } // サーバー識別名の書き込み func writeSystemName(systemName: String?) { diff --git a/AgileWorks/Common/WebClient/GetSessionEndpoint.swift b/AgileWorks/Common/WebClient/GetSessionEndpoint.swift index b36a22b56cf70a15eecef040db6653a1ccf2b23b..fa6590794742a652d00e147439d4499b27e2537d 100644 --- a/AgileWorks/Common/WebClient/GetSessionEndpoint.swift +++ b/AgileWorks/Common/WebClient/GetSessionEndpoint.swift @@ -32,10 +32,29 @@ extension GetSessionEndpoint { do { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase - return try decoder.decode(SessionInfo.self, from: object as! Data) + // 新規JSESSIONID生成のとき、cookieを保存する + let decodedSessionInfo = try decoder.decode(SessionInfo.self, from: object as! Data) + let bodySessionId = decodedSessionInfo.sessionId + let headers = urlResponse.allHeaderFields[AnyHashable("Set-Cookie")] as! String + let headerSessionId = extractSessionId(from: headers) + if headerSessionId == bodySessionId { + print("CookieHeaders: \(headers)") + KeychainDataStore().writeCookieHeader(cookieHeader: headers) + } + return decodedSessionInfo } catch { log.error(error) throw ResponseError.unexpectedObject(object) } } + func extractSessionId(from cookieString: String) -> String?{ + let components = cookieString.components(separatedBy: ";") + for component in components { + let trimmedComponent = component.trimmingCharacters(in: .whitespaces) + if trimmedComponent.hasPrefix("JSESSIONID="){ + return String(trimmedComponent.dropFirst("JSESSIONID=".count)) + } + } + return nil + } }