Revision control

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
import Foundation
6
import Shared
7
import XCGLogger
8
import SwiftyJSON
9
10
private let log = Logger.syncLogger
11
12
open class LoginPayload: CleartextPayloadJSON {
13
fileprivate static let OptionalStringFields = [
14
"formSubmitURL",
15
"httpRealm",
16
]
17
18
fileprivate static let OptionalNumericFields = [
19
"timeLastUsed",
20
"timeCreated",
21
"timePasswordChanged",
22
"timesUsed",
23
]
24
25
fileprivate static let RequiredStringFields = [
26
"hostname",
27
"username",
28
"password",
29
"usernameField",
30
"passwordField",
31
]
32
33
open class func fromJSON(_ json: JSON) -> LoginPayload? {
34
let p = LoginPayload(json)
35
if p.isValid() {
36
return p
37
}
38
return nil
39
}
40
41
override open func isValid() -> Bool {
42
if !super.isValid() {
43
return false
44
}
45
46
if self["deleted"].bool ?? false {
47
return true
48
}
49
50
if !LoginPayload.RequiredStringFields.every({ self[$0].isString() }) {
51
return false
52
}
53
54
if !LoginPayload.OptionalStringFields.every({ field in
55
let val = self[field]
56
// Yup, 404 is not found, so this means "string or nothing".
57
let valid = val.isString() || val.isNull() || val.error?.errorCode == 404
58
if !valid {
59
log.debug("Field \(field) is invalid: \(val)")
60
}
61
return valid
62
}) {
63
return false
64
}
65
66
if !LoginPayload.OptionalNumericFields.every({ field in
67
let val = self[field]
68
// Yup, 404 is not found, so this means "number or nothing".
69
// We only check for number because we're including timestamps as NSNumbers.
70
let valid = val.isNumber() || val.isNull() || val.error?.errorCode == 404
71
if !valid {
72
log.debug("Field \(field) is invalid: \(val)")
73
}
74
return valid
75
}) {
76
return false
77
}
78
79
return true
80
}
81
82
open var hostname: String {
83
return self["hostname"].string!
84
}
85
86
open var username: String {
87
return self["username"].string!
88
}
89
90
open var password: String {
91
return self["password"].string!
92
}
93
94
open var usernameField: String {
95
return self["usernameField"].string!
96
}
97
98
open var passwordField: String {
99
return self["passwordField"].string!
100
}
101
102
open var formSubmitURL: String? {
103
return self["formSubmitURL"].string
104
}
105
106
open var httpRealm: String? {
107
return self["httpRealm"].string
108
}
109
110
fileprivate func timestamp(_ field: String) -> Timestamp? {
111
let json = self[field]
112
if let i = json.int64, i > 0 {
113
return Timestamp(i)
114
}
115
return nil
116
}
117
118
open var timesUsed: Int? {
119
return self["timesUsed"].int
120
}
121
122
open var timeCreated: Timestamp? {
123
return self.timestamp("timeCreated")
124
}
125
126
open var timeLastUsed: Timestamp? {
127
return self.timestamp("timeLastUsed")
128
}
129
130
open var timePasswordChanged: Timestamp? {
131
return self.timestamp("timePasswordChanged")
132
}
133
134
override open func equalPayloads(_ obj: CleartextPayloadJSON) -> Bool {
135
if let p = obj as? LoginPayload {
136
if !super.equalPayloads(p) {
137
return false
138
}
139
140
if p.deleted || self.deleted {
141
return self.deleted == p.deleted
142
}
143
144
// If either record is deleted, these other fields might be missing.
145
// But we just checked, so we're good to roll on.
146
147
return LoginPayload.RequiredStringFields.every({ field in
148
p[field].string == self[field].string
149
})
150
151
// TODO: optional fields.
152
}
153
154
return false
155
}
156
}