From 1cb36ee385e95d25ee3d67bc611c30629fd7281c Mon Sep 17 00:00:00 2001 From: Azuma Kasumi Date: Tue, 4 Jul 2023 12:39:33 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=E3=82=AF=E3=83=A9=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=83=B3=E3=83=88=E8=A8=BC=E6=98=8E=E6=9B=B8=20=E6=9B=B8?= =?UTF-8?q?=E3=81=8D=E8=BE=BC=E3=81=BF/=E8=AA=AD=E3=81=BF=E8=BE=BC?= =?UTF-8?q?=E3=81=BF/=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AgileWorks/AgileWorks/App/AppDelegate.swift | 2 +- .../Common/Service/DeviceService.swift | 2 +- .../Login/View/LoginViewController.swift | 4 +- .../WebView/View/WebViewController.swift | 2 +- .../DataStore/CertificateDataStore.swift | 79 ++++++++++++++++--- .../Common/DataStore/KeychainDataStore.swift | 15 ++++ .../Utility/URLAuthenticationUtility.swift | 5 +- AgileWorks/Common/WebClient/Session.swift | 16 ++-- .../View/ShareViewController.swift | 2 +- 9 files changed, 104 insertions(+), 23 deletions(-) diff --git a/AgileWorks/AgileWorks/App/AppDelegate.swift b/AgileWorks/AgileWorks/App/AppDelegate.swift index 468a5ca..90cefab 100644 --- a/AgileWorks/AgileWorks/App/AppDelegate.swift +++ b/AgileWorks/AgileWorks/App/AppDelegate.swift @@ -60,7 +60,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } private func setupAppAuth() { - OIDURLSessionProvider.setSession(Session.shared) + OIDURLSessionProvider.setSession(Session.shared(serverNumber: nil)) } private func getTapLink(url: URL) -> String? { diff --git a/AgileWorks/AgileWorks/Common/Service/DeviceService.swift b/AgileWorks/AgileWorks/Common/Service/DeviceService.swift index d2ee712..fda7423 100644 --- a/AgileWorks/AgileWorks/Common/Service/DeviceService.swift +++ b/AgileWorks/AgileWorks/Common/Service/DeviceService.swift @@ -69,7 +69,7 @@ public class DeviceService { let request = DeviceRequest(deleteFlag: true) let deleteDevice = DeleteDeviceEndpoint(request: request, serverNumber: serverNumber) - Session.send(deleteDevice) { result in + Session.send(serverNumber: serverNumber, deleteDevice) { result in let completionArg: APIResult switch result { case .success: diff --git a/AgileWorks/AgileWorks/Login/View/LoginViewController.swift b/AgileWorks/AgileWorks/Login/View/LoginViewController.swift index 7ea78a9..99ffad2 100644 --- a/AgileWorks/AgileWorks/Login/View/LoginViewController.swift +++ b/AgileWorks/AgileWorks/Login/View/LoginViewController.swift @@ -85,7 +85,7 @@ class LoginViewController: UIViewController { } } - clientCertificateSettingView.isHidden = (CertificateDataStore().readClientCertificate() == nil) + clientCertificateSettingView.isHidden = (CertificateDataStore().readClientCertificate(label: nil) == nil) //固定文言表示 setupFixedWording() @@ -362,7 +362,7 @@ class LoginViewController: UIViewController { @IBAction private func clientCertificateRemovalTapped(_ sender: Any) { let alertController = UIAlertController(title: "", message: getLocalizableStrings(key: "ClientCertificateRemovalConfirmation", comment: ""), preferredStyle: .alert) let okAction = UIAlertAction(title: getLocalizableStrings(key: "ClientCertificateRemovalOK", comment: ""), style: .default) { _ in - CertificateDataStore().removeClientCertificate() + CertificateDataStore().removeClientCertificate(label: "") self.clientCertificateSettingView.isHidden = true } let cancelAction = UIAlertAction(title: getLocalizableStrings(key: "ClientCertificateRemovalCancel", comment: ""), style: .cancel, handler: nil) diff --git a/AgileWorks/AgileWorks/WebView/View/WebViewController.swift b/AgileWorks/AgileWorks/WebView/View/WebViewController.swift index 008af00..6fdb282 100644 --- a/AgileWorks/AgileWorks/WebView/View/WebViewController.swift +++ b/AgileWorks/AgileWorks/WebView/View/WebViewController.swift @@ -603,7 +603,7 @@ extension WebViewController: WKNavigationDelegate { private func download(url: URL, saveAs fileName: String) { var request = downloadRequest(url: url) var tempPath: URL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName) - Session.shared.dataTask(with: request) { data, res, err in + Session.shared(serverNumber: nil).dataTask(with: request) { data, res, err in guard let data = data, err == nil else { log.e(err) return diff --git a/AgileWorks/Common/DataStore/CertificateDataStore.swift b/AgileWorks/Common/DataStore/CertificateDataStore.swift index 45f08c4..8bf677c 100644 --- a/AgileWorks/Common/DataStore/CertificateDataStore.swift +++ b/AgileWorks/Common/DataStore/CertificateDataStore.swift @@ -17,27 +17,34 @@ let kClientCertLabel = "ClientCert" /// その実装に用いている `Keychain` は `kSecIdentity` の読み書きに対応していないため別途定義。 class CertificateDataStore { /// クライアント証明書を読み込む。取得できなければ `nil` 。 - func readClientCertificate() -> SecIdentity? { - return readIdentity(label: kClientCertLabel) + func readClientCertificate(label: String?) -> SecIdentity? { + guard let label else { + return nil + } + return readIdentity(label: label) + } + func readAllClientCertificate() -> [NSDictionary]? { + return readAllIdentity() } /// クライアント証明書を利用した `URLCredential` (セッション内で有効) を取得する。取得できなければ `nil`。 - func getClientCertificateSessionCredential() -> URLCredential? { - if let identity = readClientCertificate() { + func getClientCertificateSessionCredential(label: String?) -> URLCredential? { + if let identity = readClientCertificate(label: label) { return sessionCredential(identity: identity) } else { return nil } } - /// クライアント証明書を書き込む。 - func writeClientCertificate(identity: SecIdentity) { - writeIdentity(label: kClientCertLabel, identity: identity) + //証明書の書き込み + func writeNewClientCertificate(identity: SecIdentity) { + var newLabel = kClientCertLabel + serchLabelID() + writeIdentity(label: newLabel, identity: identity) } /// クライアント証明書を削除する。 - func removeClientCertificate() { - removeIdentity(label: kClientCertLabel) + func removeClientCertificate(label: String) { + removeIdentity(label: label) } private func readIdentity(label: String) -> SecIdentity? { @@ -69,6 +76,36 @@ class CertificateDataStore { } } + //証明書リスト呼び出し + private func readAllIdentity() -> [NSDictionary]? { + let query: [String: Any] = [ + kSecClass as String: kSecClassIdentity, + //kSecAttrLabel as String: label, + kSecReturnRef as String: true, + kSecReturnPersistentRef as String: true, + kSecMatchLimit as String: kSecMatchLimitAll + ] + var result: CFTypeRef? = nil + let status = SecItemCopyMatching(query as CFDictionary, &result) + switch status { + case errSecSuccess: + if let result = result { + let array = (result as? NSArray) as? [NSDictionary] + return array + } else { + log.error("unexpected nil result") + return nil + } + + case errSecItemNotFound: + return nil + + default: + log.error("keychain reading error: OSStatus = \(status)") + return nil + } + } + private func writeIdentity(label: String, identity: SecIdentity) { let query: [String: Any] = [ kSecValueRef as String: identity, @@ -80,6 +117,29 @@ class CertificateDataStore { } } + private func serchLabelID() -> String { + var labelID = 0 + var flg = true + while flg { + let label = kClientCertLabel + String(labelID) + let query: [String: Any] = [ + kSecClass as String: kSecClassIdentity, + kSecAttrLabel as String: label, + kSecReturnRef as String: true, + kSecReturnPersistentRef as String: true + ] + var result: CFTypeRef? = nil + let status = SecItemCopyMatching(query as CFDictionary, &result) + switch status { + case errSecItemNotFound: + flg = false + default: + labelID += 1 + } + } + return String(labelID) + } + private func removeIdentity(label: String) { let query: [String: Any] = [ kSecClass as String: kSecClassIdentity, @@ -95,3 +155,4 @@ class CertificateDataStore { return URLCredential(identity: identity, certificates: nil, persistence: .forSession) } } + diff --git a/AgileWorks/Common/DataStore/KeychainDataStore.swift b/AgileWorks/Common/DataStore/KeychainDataStore.swift index cd7b89e..ae48cb1 100644 --- a/AgileWorks/Common/DataStore/KeychainDataStore.swift +++ b/AgileWorks/Common/DataStore/KeychainDataStore.swift @@ -20,6 +20,7 @@ final class KeychainDataStore: DataStoreProtocol { private let kDeviceID: String = "DeviceID" private let kSessionID: String = "SessionID" private let kServerName: String = "ServerName" + private let kCertificateLabel: String = "CertificateLabel" // アクセストークンの書き込み func writeAccessToken(accessToken: String) { @@ -186,6 +187,20 @@ final class KeychainDataStore: DataStoreProtocol { removeKeychainValue(key: kServerName) } + func writeCertificateLabel(label: String) { + setKeychainValue(key: kCertificateLabel, value: label) + } + + func readCertificateLabel(serverNumber: Int?) -> String? { + if let serverNumber = serverNumber { + return getKeychainValue(key: kCertificateLabel, serverNumber: serverNumber) + } + return getKeychainValue(key: kCertificateLabel) + } + + func removeCertificateLabel() { + removeKeychainValue(key: kCertificateLabel) + } // KeyChain へ指定の値をセット private func setKeychainValue(key: String, value: String) { let keychain = Keychain(service: service, accessGroup: UserDefaultsDataStore().readGroupId()!) diff --git a/AgileWorks/Common/Utility/URLAuthenticationUtility.swift b/AgileWorks/Common/Utility/URLAuthenticationUtility.swift index cd151b6..3eadaec 100644 --- a/AgileWorks/Common/Utility/URLAuthenticationUtility.swift +++ b/AgileWorks/Common/Utility/URLAuthenticationUtility.swift @@ -8,12 +8,13 @@ import Foundation -func handleURLAuthenticationChallenge(_ challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { +func handleURLAuthenticationChallenge(_ challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void, serverNumber: Int? = nil) { var handled = false switch challenge.protectionSpace.authenticationMethod { case NSURLAuthenticationMethodClientCertificate: - if let credential = CertificateDataStore().getClientCertificateSessionCredential() { + var certificateLabel = KeychainDataStore().readCertificateLabel(serverNumber: serverNumber) + if let credential = CertificateDataStore().getClientCertificateSessionCredential(label: certificateLabel) { completionHandler(.useCredential, credential) handled = true } diff --git a/AgileWorks/Common/WebClient/Session.swift b/AgileWorks/Common/WebClient/Session.swift index da0f712..bb3886b 100644 --- a/AgileWorks/Common/WebClient/Session.swift +++ b/AgileWorks/Common/WebClient/Session.swift @@ -17,25 +17,29 @@ enum SessionError: Error { class Session { private static let kRetryCount = 3 - private static let privateShared = URLSession(configuration: .default, delegate: SessionDelegate(), delegateQueue: nil) + //private static let privateShared = URLSession(configuration: .default, delegate: SessionDelegate(), delegateQueue: nil) private static var retryCount = kRetryCount - class var shared: URLSession { + class func shared(serverNumber: Int? = nil) -> URLSession { + var sessionDelegate = SessionDelegate() + + sessionDelegate.serverNumber = serverNumber + let privateShared = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil) return privateShared } private class SessionDelegate: NSObject, URLSessionDelegate { + var serverNumber: Int? = nil func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - handleURLAuthenticationChallenge(challenge, completionHandler: completionHandler) + handleURLAuthenticationChallenge(challenge, completionHandler: completionHandler, serverNumber: serverNumber) } } @discardableResult - class func send(_ request: T, completion: @escaping (Result) -> Void) -> URLSessionTask? { + class func send(serverNumber: Int? = nil, _ request: T, completion: @escaping (Result) -> Void) -> URLSessionTask? { let req = request.request log.debug("Session Request: \(String(describing: req.url))\nHTTPMethod: \(String(describing: req.httpMethod) )\nHeaders: \(String(describing: req.allHTTPHeaderFields))\nHTTPBody: \(String(data: req.httpBody ?? Data(), encoding: .utf8)!)") - - let task = shared.dataTask(with: req) { data, response, error in + let task = shared(serverNumber: serverNumber).dataTask(with: req) { data, response, error in let result: Result let res = response as? HTTPURLResponse diff --git a/AgileWorks/ShareExtension/View/ShareViewController.swift b/AgileWorks/ShareExtension/View/ShareViewController.swift index b0be7d8..7d7a2d5 100644 --- a/AgileWorks/ShareExtension/View/ShareViewController.swift +++ b/AgileWorks/ShareExtension/View/ShareViewController.swift @@ -47,7 +47,7 @@ class ShareViewController: UITableViewController { @IBAction func onImportButtonTapped(_ sender: UIBarButtonItem) { importCertificateFromContext() { identity, error in if let identity = identity { - CertificateDataStore().writeClientCertificate(identity: identity) + CertificateDataStore().writeNewClientCertificate(identity: identity) self.alertInUiThread(message: getDisplayString(key: "CertificateImportSuccess", comment: "")) { self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) } -- GitLab From 6837c3f94006fb1be43d604a6f999349c2afb49b Mon Sep 17 00:00:00 2001 From: Azuma Kasumi Date: Wed, 5 Jul 2023 13:14:59 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E8=A8=BC=E6=98=8E=E6=9B=B8=E3=81=AE?= =?UTF-8?q?=E6=9B=B8=E3=81=8D=E8=BE=BC=E3=81=BF=E3=83=BB=E8=AA=AD=E3=81=BF?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E3=83=BB=E5=89=8A=E9=99=A4=20UI=E9=80=A3?= =?UTF-8?q?=E6=90=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AgileWorks.xcodeproj/project.pbxproj | 4 + .../ClientCertificateViewController.swift | 96 +++++++++++++++++++ .../Login/View/LoginViewController.storyboard | 85 +++++++++++----- .../Login/View/LoginViewController.swift | 33 ++++++- .../Strings/Chinese-Simplified.strings | 1 + .../Strings/Chinese-Traditional.strings | 1 + AgileWorks/AgileWorks/Strings/English.strings | 1 + .../AgileWorks/Strings/Japanese.strings | 1 + .../Strings/en.lproj/Localizable.strings | 1 + .../Strings/ja.lproj/Localizable.strings | 1 + .../Strings/zh-Hans.lproj/Localizable.strings | 1 + .../Strings/zh-Hant.lproj/Localizable.strings | 1 + .../DataStore/CertificateDataStore.swift | 21 ++-- .../Common/DataStore/KeychainDataStore.swift | 2 +- .../View/ShareViewController.swift | 16 +++- 15 files changed, 227 insertions(+), 38 deletions(-) create mode 100644 AgileWorks/AgileWorks/Login/View/ClientCertificateViewController.swift diff --git a/AgileWorks/AgileWorks.xcodeproj/project.pbxproj b/AgileWorks/AgileWorks.xcodeproj/project.pbxproj index ce4ac23..db10737 100644 --- a/AgileWorks/AgileWorks.xcodeproj/project.pbxproj +++ b/AgileWorks/AgileWorks.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ 7549FC9227EAF42C00FF9E0C /* Chinese-Simplified.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917F9F27BDF2050051E201 /* Chinese-Simplified.strings */; }; 7549FC9327EAF42C00FF9E0C /* Chinese-Traditional.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917FA127BDF2250051E201 /* Chinese-Traditional.strings */; }; 756C7E7B282B6A5700C24F4D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 756C7E7A282B6A5700C24F4D /* Assets.xcassets */; }; + 758F1BC02A5408D400DDEBC5 /* ClientCertificateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 758F1BBF2A5408D400DDEBC5 /* ClientCertificateViewController.swift */; }; 75917F9C27BDF0F10051E201 /* Japanese.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917F9B27BDF0F10051E201 /* Japanese.strings */; }; 75917F9E27BDF1C30051E201 /* English.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917F9D27BDF1C30051E201 /* English.strings */; }; 75917FA027BDF2050051E201 /* Chinese-Simplified.strings in Resources */ = {isa = PBXBuildFile; fileRef = 75917F9F27BDF2050051E201 /* Chinese-Simplified.strings */; }; @@ -279,6 +280,7 @@ 7576916A2A2EFEEC00EAFCBC /* AgileWorksRelease-Production.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "AgileWorksRelease-Production.entitlements"; sourceTree = ""; }; 7576916B2A2F003400EAFCBC /* TodayExtensionRelease-Production.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "TodayExtensionRelease-Production.entitlements"; sourceTree = ""; }; 757691732A32E5F100EAFCBC /* WidgetExtensionRelease-Production.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WidgetExtensionRelease-Production.entitlements"; sourceTree = ""; }; + 758F1BBF2A5408D400DDEBC5 /* ClientCertificateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientCertificateViewController.swift; sourceTree = ""; }; 75917F9227BD315A0051E201 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 75917F9627BD317E0051E201 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = ""; }; 75917F9B27BDF0F10051E201 /* Japanese.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Japanese.strings; sourceTree = ""; }; @@ -680,6 +682,7 @@ children = ( VRZQMTKT1ZML0OSDGAKBK8ID /* LoginViewController.storyboard */, TXU9U01HBM9MEYSGQITWCSE9 /* LoginViewController.swift */, + 758F1BBF2A5408D400DDEBC5 /* ClientCertificateViewController.swift */, ); path = View; sourceTree = ""; @@ -1583,6 +1586,7 @@ UMOPI6WFDHHR8CWFUZDQE9U2 /* LoginUseCase.swift in Sources */, BD6C1AED24A480C70057756F /* DeviceService.swift in Sources */, 76AE530025D3626800AFA45A /* LicenseViewController.swift in Sources */, + 758F1BC02A5408D400DDEBC5 /* ClientCertificateViewController.swift in Sources */, BDAB1F6D240D0D0000EA15FD /* UserDefaultsExtensions.swift in Sources */, C50CF78E27D86DC90042C210 /* OpenLicenseBuilder.swift in Sources */, BD969631240C9CD400521925 /* SessionInfo.swift in Sources */, diff --git a/AgileWorks/AgileWorks/Login/View/ClientCertificateViewController.swift b/AgileWorks/AgileWorks/Login/View/ClientCertificateViewController.swift new file mode 100644 index 0000000..c80a688 --- /dev/null +++ b/AgileWorks/AgileWorks/Login/View/ClientCertificateViewController.swift @@ -0,0 +1,96 @@ +// +// ClientCertificateTableViewController.swift +// AgileWorks +// +// Created by Azuma Kasumi on 2023/07/04. +// Copyright © 2023 ATLED CORP. All rights reserved. +// + +import UIKit + +class ClientCertificateViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + @IBOutlet weak var tableView: UITableView! + //インポート済みの証明書リスト + var certificates = CertificateDataStore().readAllClientCertificate() + override func viewDidLoad() { + super.viewDidLoad() + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") + + tableView.dataSource = self + tableView.delegate = self + + tableView.allowsMultipleSelection = false + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) + + cell.selectionStyle = UITableViewCell.SelectionStyle.none + + //ラベル名取得 + let dic = certificates![indexPath.row] + let label = dic[kSecAttrLabel as String] as! String + cell.textLabel?.text = label + + //選択中のラベルの場合はチェックマークをつける + let selectedLabel = KeychainDataStore().readCertificateLabel() + if selectedLabel == label { + cell.accessoryType = .checkmark + self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none) + } + return cell + } + + //セルの数 + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if certificates == nil { + return 0 + } + return certificates!.count + } + + // セルが選択された時に呼び出される + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let cell = tableView.cellForRow(at: indexPath) + // チェックマークを入れる + cell!.accessoryType = .checkmark + + let dic = certificates![indexPath.row] + let label = dic[kSecAttrLabel as String] as! String + + let selectedLabel = KeychainDataStore().readCertificateLabel() + if selectedLabel != label { + //キーチェーンにラベル名を保存 + KeychainDataStore().writeCertificateLabel(label: label) + } else { + //設定済みの場合は解除 + cell!.accessoryType = .none + KeychainDataStore().removeCertificateLabel() + } + } + + // セルの選択が外れた時に呼び出される + func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) { + let cell = tableView.cellForRow(at: indexPath) + // チェックマークを外す + cell!.accessoryType = .none + } + + //クライアント証明書をスワイプ削除 + func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + let dic = certificates![indexPath.row] + let label = dic[kSecAttrLabel as String] as! String + CertificateDataStore().removeClientCertificate(label: label) + + //選択中の証明書が削除された場合 + let selectedLabel = KeychainDataStore().readCertificateLabel() + if selectedLabel == label { + KeychainDataStore().removeCertificateLabel() + } + certificates = CertificateDataStore().readAllClientCertificate() + + tableView.deleteRows(at: [indexPath], with: .automatic) + } + } +} diff --git a/AgileWorks/AgileWorks/Login/View/LoginViewController.storyboard b/AgileWorks/AgileWorks/Login/View/LoginViewController.storyboard index ae4f5c3..422edd7 100644 --- a/AgileWorks/AgileWorks/Login/View/LoginViewController.storyboard +++ b/AgileWorks/AgileWorks/Login/View/LoginViewController.storyboard @@ -1,9 +1,9 @@ - + - + @@ -19,17 +19,17 @@ - + - + - + - + @@ -95,21 +95,31 @@ - + @@ -208,7 +218,7 @@ - + @@ -227,11 +237,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - @@ -245,7 +286,7 @@ - + diff --git a/AgileWorks/AgileWorks/Login/View/LoginViewController.swift b/AgileWorks/AgileWorks/Login/View/LoginViewController.swift index 99ffad2..e96cf0a 100644 --- a/AgileWorks/AgileWorks/Login/View/LoginViewController.swift +++ b/AgileWorks/AgileWorks/Login/View/LoginViewController.swift @@ -30,7 +30,7 @@ class LoginViewController: UIViewController { @IBOutlet private var disablingView: UIView! @IBOutlet private var clientCertificateLabel: UILabel! @IBOutlet private var clientCertificateSettingView: UIView! - @IBOutlet private var clientCertificateRemovalButton: UIButton! + @IBOutlet weak var clientCertificateSelectButton: UIButton! @IBOutlet private var qrCodeReadLabel: UILabel! @IBOutlet private var qrCodeReadView: UIView! @IBOutlet private var qrCodeStartupButton: UIButton! @@ -52,6 +52,8 @@ class LoginViewController: UIViewController { return context } + private var navVC: UINavigationController? = nil + let qrCodeReader = QRCodeReader() func inject(presenter: LoginPresenter) { @@ -85,7 +87,9 @@ class LoginViewController: UIViewController { } } - clientCertificateSettingView.isHidden = (CertificateDataStore().readClientCertificate(label: nil) == nil) + let certificates = CertificateDataStore().readAllClientCertificate() + clientCertificateSettingView.isHidden = certificates?.isEmpty ?? true + //固定文言表示 setupFixedWording() @@ -174,6 +178,26 @@ class LoginViewController: UIViewController { self.view.endEditing(true) } + @IBAction func clientCertificateSelectButtonTapped(_ sender: Any) { + let csVC = clientCertificateSelectView() + navVC = UINavigationController(rootViewController: csVC) + //navVC.setNavigationBarHidden(false, animated: true) + let chevronLeftImage: UIImage? = UIImage(systemName: "chevron.left") + let backButton = UIBarButtonItem(image: chevronLeftImage, style: .plain, target: self, action: #selector(onTapBackButton(_:))) + navVC!.navigationBar.topItem!.leftBarButtonItem = backButton + navVC!.modalPresentationStyle = .fullScreen + self.present(navVC!, animated: true) + } + @objc func onTapBackButton(_ sender: UIBarButtonItem) { + navVC?.dismiss(animated: true) + } + + private func clientCertificateSelectView() -> UIViewController { + let storyboard = UIStoryboard(name: "LoginViewController", bundle: nil) + let vc = storyboard.instantiateViewController(withIdentifier: "ClientCertificateTableViewController") + return vc + } + @IBAction private func loginTapped() { guard let serverHost = serverHost else { return } guard let context = context else { return } @@ -339,7 +363,7 @@ class LoginViewController: UIViewController { private func setLoginControlesEnabled(_ enabled: Bool) { disablingView.isHidden = enabled - let controls: [UIControl] = [serverTextField, contextTextField, loginButton, clientCertificateRemovalButton, qrCodeStartupButton] + let controls: [UIControl] = [serverTextField, contextTextField, loginButton, clientCertificateSelectButton, qrCodeStartupButton] for view in controls { view.isEnabled = enabled } @@ -359,6 +383,7 @@ class LoginViewController: UIViewController { } } + @IBAction private func clientCertificateRemovalTapped(_ sender: Any) { let alertController = UIAlertController(title: "", message: getLocalizableStrings(key: "ClientCertificateRemovalConfirmation", comment: ""), preferredStyle: .alert) let okAction = UIAlertAction(title: getLocalizableStrings(key: "ClientCertificateRemovalOK", comment: ""), style: .default) { _ in @@ -370,7 +395,7 @@ class LoginViewController: UIViewController { alertController.addAction(cancelAction) present(alertController, animated: true, completion: nil) } - + private func devcie(serverNumber: Int, completion: @escaping (APIResult) -> Void) { DeviceService().deleteDevice(serverNumber: serverNumber) { result in switch result { diff --git a/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings b/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings index f522d3b..a3ae55f 100644 --- a/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings +++ b/AgileWorks/AgileWorks/Strings/Chinese-Simplified.strings @@ -104,6 +104,7 @@ "CertificateImportSuccess" = "输入的客户证书。"; "IncorrectPassphrase" = "通行证短语是不同的。"; "CertificateImportError" = "在证书导入过程中发生了一个错误。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "草稿"; diff --git a/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings b/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings index 32cafea..583c9aa 100644 --- a/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings +++ b/AgileWorks/AgileWorks/Strings/Chinese-Traditional.strings @@ -104,6 +104,7 @@ "CertificateImportSuccess" = "輸入的客戶證書。"; "IncorrectPassphrase" = "通行證短語是不同的。"; "CertificateImportError" = "在證書導入過程中發生了一個錯誤。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "草稿"; diff --git a/AgileWorks/AgileWorks/Strings/English.strings b/AgileWorks/AgileWorks/Strings/English.strings index 91107a4..1aa6030 100644 --- a/AgileWorks/AgileWorks/Strings/English.strings +++ b/AgileWorks/AgileWorks/Strings/English.strings @@ -104,6 +104,7 @@ "CertificateImportSuccess" = "Client certificate imported."; "IncorrectPassphrase" = "The passphrase is different."; "CertificateImportError" = "An error occurred during certificate import."; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "Draft"; diff --git a/AgileWorks/AgileWorks/Strings/Japanese.strings b/AgileWorks/AgileWorks/Strings/Japanese.strings index 5219e3c..9a7dbfc 100644 --- a/AgileWorks/AgileWorks/Strings/Japanese.strings +++ b/AgileWorks/AgileWorks/Strings/Japanese.strings @@ -104,6 +104,7 @@ "CertificateImportSuccess" = "クライアント証明書をインポートしました。"; "IncorrectPassphrase" = "パスフレーズが違います。"; "CertificateImportError" = "証明書のインポート中にエラーが発生しました。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "下書き"; diff --git a/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings index c2874e3..eced434 100644 --- a/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/en.lproj/Localizable.strings @@ -104,6 +104,7 @@ "CertificateImportSuccess" = "Client certificate imported."; "IncorrectPassphrase" = "The passphrase is different."; "CertificateImportError" = "An error occurred during certificate import."; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "Draft"; diff --git a/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings index 2d54811..0575be7 100644 --- a/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/ja.lproj/Localizable.strings @@ -104,6 +104,7 @@ "CertificateImportSuccess" = "クライアント証明書をインポートしました。"; "IncorrectPassphrase" = "パスフレーズが違います。"; "CertificateImportError" = "証明書のインポート中にエラーが発生しました。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "下書き"; diff --git a/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings index 55c49a3..8861234 100644 --- a/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/zh-Hans.lproj/Localizable.strings @@ -104,6 +104,7 @@ "CertificateImportSuccess" = "输入的客户证书。"; "IncorrectPassphrase" = "通行证短语是不同的。"; "CertificateImportError" = "在证书导入过程中发生了一个错误。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "草稿"; diff --git a/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings b/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings index a818da8..3568da2 100644 --- a/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings +++ b/AgileWorks/AgileWorks/Strings/zh-Hant.lproj/Localizable.strings @@ -104,6 +104,7 @@ "CertificateImportSuccess" = "輸入的客戶證書。"; "IncorrectPassphrase" = "通行證短語是不同的。"; "CertificateImportError" = "在證書導入過程中發生了一個錯誤。"; +"CertificateStorageLimit" = "証明書の保存上限を超えました。ログイン画面から削除してください。"; // Widget Status "Draft" = "草稿"; diff --git a/AgileWorks/Common/DataStore/CertificateDataStore.swift b/AgileWorks/Common/DataStore/CertificateDataStore.swift index 8bf677c..19f4f3b 100644 --- a/AgileWorks/Common/DataStore/CertificateDataStore.swift +++ b/AgileWorks/Common/DataStore/CertificateDataStore.swift @@ -37,9 +37,15 @@ class CertificateDataStore { } //証明書の書き込み - func writeNewClientCertificate(identity: SecIdentity) { - var newLabel = kClientCertLabel + serchLabelID() - writeIdentity(label: newLabel, identity: identity) + func writeNewClientCertificate(identity: SecIdentity) -> String? { + let labelID = serchLabelID() + if let labelID = labelID { + var newLabel = kClientCertLabel + labelID + writeIdentity(label: newLabel, identity: identity) + } else { + log.error("Certificate storage limit exceeded") + } + return labelID } /// クライアント証明書を削除する。 @@ -117,10 +123,9 @@ class CertificateDataStore { } } - private func serchLabelID() -> String { + private func serchLabelID() -> String? { var labelID = 0 - var flg = true - while flg { + while labelID < 20 { let label = kClientCertLabel + String(labelID) let query: [String: Any] = [ kSecClass as String: kSecClassIdentity, @@ -132,12 +137,12 @@ class CertificateDataStore { let status = SecItemCopyMatching(query as CFDictionary, &result) switch status { case errSecItemNotFound: - flg = false + return String(labelID) default: labelID += 1 } } - return String(labelID) + return nil } private func removeIdentity(label: String) { diff --git a/AgileWorks/Common/DataStore/KeychainDataStore.swift b/AgileWorks/Common/DataStore/KeychainDataStore.swift index ae48cb1..dd6a43e 100644 --- a/AgileWorks/Common/DataStore/KeychainDataStore.swift +++ b/AgileWorks/Common/DataStore/KeychainDataStore.swift @@ -191,7 +191,7 @@ final class KeychainDataStore: DataStoreProtocol { setKeychainValue(key: kCertificateLabel, value: label) } - func readCertificateLabel(serverNumber: Int?) -> String? { + func readCertificateLabel(serverNumber: Int? = nil) -> String? { if let serverNumber = serverNumber { return getKeychainValue(key: kCertificateLabel, serverNumber: serverNumber) } diff --git a/AgileWorks/ShareExtension/View/ShareViewController.swift b/AgileWorks/ShareExtension/View/ShareViewController.swift index 7d7a2d5..e0086a8 100644 --- a/AgileWorks/ShareExtension/View/ShareViewController.swift +++ b/AgileWorks/ShareExtension/View/ShareViewController.swift @@ -47,9 +47,19 @@ class ShareViewController: UITableViewController { @IBAction func onImportButtonTapped(_ sender: UIBarButtonItem) { importCertificateFromContext() { identity, error in if let identity = identity { - CertificateDataStore().writeNewClientCertificate(identity: identity) - self.alertInUiThread(message: getDisplayString(key: "CertificateImportSuccess", comment: "")) { - self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + //書き込み + let result = CertificateDataStore().writeNewClientCertificate(identity: identity) + + if let _ = result { + //成功 + self.alertInUiThread(message: getDisplayString(key: "CertificateImportSuccess", comment: "")) { + self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil) + } + } else { + //保存上限オーバー + self.alertInUiThread(message: getDisplayString(key: "CertificateStorageLimit", comment: "")) { + self.extensionContext?.cancelRequest(withError: error ?? ShareExtensionError.genericError) + } } } else { if let error = error as NSError?, -- GitLab