diff --git a/Chronos.xcodeproj/project.pbxproj b/Chronos.xcodeproj/project.pbxproj index 2327a06..deccd08 100644 --- a/Chronos.xcodeproj/project.pbxproj +++ b/Chronos.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 6B7383E02B9C3962008E8867 /* Factory in Frameworks */ = {isa = PBXBuildFile; productRef = 6B7383DF2B9C3962008E8867 /* Factory */; }; 6B7383E52B9C4230008E8867 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7383E42B9C4230008E8867 /* Container.swift */; }; 6B7A54742B94B7A30057DCF9 /* PrivacyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B7A54732B94B7A30057DCF9 /* PrivacyView.swift */; }; + 6B8132F22C4975DA00DB367E /* Raivo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B8132F12C4975DA00DB367E /* Raivo.swift */; }; 6B842DD52BE33E2E00056F0F /* RestoreBackupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B842DD42BE33E2E00056F0F /* RestoreBackupView.swift */; }; 6B8ABEEC2B8F6A4F00F5B514 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 6B8ABEEB2B8F6A4F00F5B514 /* CryptoSwift */; }; 6B9581E22B62AFF80029EF3C /* ViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9581E12B62AFF80029EF3C /* ViewModifier.swift */; }; @@ -113,6 +114,7 @@ 6B7383E42B9C4230008E8867 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = ""; }; 6B7A546F2B94584E0057DCF9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 6B7A54732B94B7A30057DCF9 /* PrivacyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyView.swift; sourceTree = ""; }; + 6B8132F12C4975DA00DB367E /* Raivo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Raivo.swift; sourceTree = ""; }; 6B842DD42BE33E2E00056F0F /* RestoreBackupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreBackupView.swift; sourceTree = ""; }; 6B9581E12B62AFF80029EF3C /* ViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifier.swift; sourceTree = ""; }; 6B9581E32B633A880029EF3C /* AddManualTokenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddManualTokenView.swift; sourceTree = ""; }; @@ -293,6 +295,7 @@ isa = PBXGroup; children = ( 6B4CBF2E2C490FB700983D44 /* Chronos.swift */, + 6B8132F12C4975DA00DB367E /* Raivo.swift */, ); path = Import; sourceTree = ""; @@ -612,6 +615,7 @@ buildActionMask = 2147483647; files = ( 6B4CBF2F2C490FB700983D44 /* Chronos.swift in Sources */, + 6B8132F22C4975DA00DB367E /* Raivo.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Chronos/App/Tabs/Settings/Import/ImportSelectionView.swift b/Chronos/App/Tabs/Settings/Import/ImportSelectionView.swift index 89663e4..bd6b47a 100644 --- a/Chronos/App/Tabs/Settings/Import/ImportSelectionView.swift +++ b/Chronos/App/Tabs/Settings/Import/ImportSelectionView.swift @@ -9,7 +9,7 @@ struct ImportSource: Identifiable { struct ImportSelectionView: View { let importSources: [ImportSource] = [ ImportSource(id: "chronos", name: "Chronos", desc: "Export your tokens from Chronos to an unencrypted JSON file, then select the file below."), -// ImportSource(id: "raivo", name: "Raivo", desc: "Export your tokens from Raivo using \"Export OTPs to ZIP archive\" option. Extract the JSON file from the archive, then select the file below."), + ImportSource(id: "raivo", name: "Raivo", desc: "Export your tokens from Raivo using \"Export OTPs to ZIP archive\" option. Extract the JSON file from the archive, then select the file below."), ] @EnvironmentObject var importNav: ExportNavigation diff --git a/Chronos/Services/ImportService.swift b/Chronos/Services/ImportService.swift index 2f16f52..2c3570a 100644 --- a/Chronos/Services/ImportService.swift +++ b/Chronos/Services/ImportService.swift @@ -16,6 +16,8 @@ public class ImportService { switch importSource.id { case "chronos": return importFromChronos(json: json) + case "raivo": + return importFromRaivo(json: json) default: logger.error("Unsupported import source: \(importSource.id)") return nil @@ -51,4 +53,47 @@ extension ImportService { return nil } } + + func importFromRaivo(json: JSON) -> [Token]? { + var tokens: [Token] = [] + + for (key, subJson) in json { + guard + let issuer = subJson["issuer"].string, + let account = subJson["account"].string, + let secret = subJson["secret"].string, + + let digitsString = subJson["digits"].string, + let digits = Int(digitsString), + + let periodString = subJson["timer"].string, + let period = Int(periodString), + + let counterString = subJson["counter"].string, + let counter = Int(counterString), + + let kind = subJson["kind"].string, + let algorithm = subJson["algorithm"].string, + let tokenType = TokenTypeEnum(rawValue: kind), + let tokenAlgorithm = TokenAlgorithmEnum(rawValue: algorithm) + else { + logger.error("Error parsing token data for key: \(key)") + continue + } + + let token = Token() + token.issuer = issuer + token.account = account + token.secret = secret + token.digits = digits + token.period = period + token.counter = counter + token.type = tokenType + token.algorithm = tokenAlgorithm + + tokens.append(token) + } + + return tokens + } } diff --git a/ChronosTests/Import/Chronos.swift b/ChronosTests/Import/Chronos.swift index 6f47c03..e344309 100644 --- a/ChronosTests/Import/Chronos.swift +++ b/ChronosTests/Import/Chronos.swift @@ -1,17 +1,8 @@ @testable import Chronos -import Factory import SwiftyJSON import XCTest final class ChronosTests: XCTestCase { - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - func testValidImport() throws { let json: JSON = [ "tokens": [ diff --git a/ChronosTests/Import/Raivo.swift b/ChronosTests/Import/Raivo.swift new file mode 100644 index 0000000..7945558 --- /dev/null +++ b/ChronosTests/Import/Raivo.swift @@ -0,0 +1,81 @@ +@testable import Chronos +import SwiftyJSON +import XCTest + +final class RaivoTests: XCTestCase { + func testValidImport() throws { + let json: JSON = [ + [ + "secret": "ff", + "timer": "30", + "account": "Test HOTP", + "kind": "HOTP", + "algorithm": "SHA1", + "digits": "6", + "pinned": "false", + "iconValue": "", + "counter": "12", + "issuer": "Raivo", + "iconType": "", + ], + [ + "issuer": "Raivo", + "secret": "JBSWY3DPEHPK3PXP", + "iconType": "", + "iconValue": "", + "pinned": "false", + "algorithm": "SHA1", + "digits": "6", + "kind": "TOTP", + "timer": "30", + "counter": "0", + "account": "Test TOTP", + ], + ] + + let importService = ImportService() + let tokens = importService.importFromRaivo(json: json)! + + XCTAssertEqual(tokens.count, 2) + + XCTAssertEqual(tokens[0].digits, 6) + XCTAssertEqual(tokens[0].type, TokenTypeEnum.HOTP) + XCTAssertEqual(tokens[0].counter, 12) + XCTAssertEqual(tokens[0].algorithm, TokenAlgorithmEnum.SHA1) + XCTAssertEqual(tokens[0].issuer, "Raivo") + XCTAssertEqual(tokens[0].account, "Test HOTP") + XCTAssertEqual(tokens[0].period, 30) + XCTAssertEqual(tokens[0].secret, "ff") + + XCTAssertEqual(tokens[1].digits, 6) + XCTAssertEqual(tokens[1].type, TokenTypeEnum.TOTP) + XCTAssertEqual(tokens[1].counter, 0) + XCTAssertEqual(tokens[1].algorithm, TokenAlgorithmEnum.SHA1) + XCTAssertEqual(tokens[1].issuer, "Raivo") + XCTAssertEqual(tokens[1].account, "Test TOTP") + XCTAssertEqual(tokens[1].period, 30) + XCTAssertEqual(tokens[1].secret, "JBSWY3DPEHPK3PXP") + } + + func testInvalidImport_MissingVariables() throws { + let json: JSON = [ + [ + "issuer": "Raivo", + "iconType": "", + "iconValue": "", + "pinned": "false", + "algorithm": "SHA1", + "digits": "6", + "kind": "TOTP", + "timer": "30", + "counter": "0", + "account": "Test TOTP", + ], + ] + + let importService = ImportService() + let tokens = importService.importFromChronos(json: json) + + XCTAssertNil(tokens) + } +}