gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/system_user_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package asserts_test 21 22 import ( 23 "fmt" 24 "strings" 25 "time" 26 27 . "gopkg.in/check.v1" 28 29 "gitee.com/mysnapcore/mysnapd/asserts" 30 ) 31 32 var ( 33 _ = Suite(&systemUserSuite{}) 34 ) 35 36 type systemUserSuite struct { 37 until time.Time 38 untilLine string 39 since time.Time 40 sinceLine string 41 userPresenceLine string 42 43 formatLine string 44 modelsLine string 45 46 systemUserStr string 47 } 48 49 const systemUserExample = "type: system-user\n" + 50 "FORMATLINE\n" + 51 "authority-id: canonical\n" + 52 "brand-id: canonical\n" + 53 "email: foo@example.com\n" + 54 "series:\n" + 55 " - 16\n" + 56 "MODELSLINE\n" + 57 "name: Nice Guy\n" + 58 "username: guy\n" + 59 "password: $6$salt$hash\n" + 60 "ssh-keys:\n" + 61 " - ssh-rsa AAAABcdefg\n" + 62 "SINCELINE\n" + 63 "UNTILLINE\n" + 64 "USERVALIDFOR\n" + 65 "body-length: 0\n" + 66 "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" + 67 "\n\n" + 68 "AXNpZw==" 69 70 func (s *systemUserSuite) SetUpTest(c *C) { 71 s.since = time.Now().Truncate(time.Second) 72 s.sinceLine = fmt.Sprintf("since: %s\n", s.since.Format(time.RFC3339)) 73 s.until = time.Now().AddDate(0, 1, 0).Truncate(time.Second) 74 s.untilLine = fmt.Sprintf("until: %s\n", s.until.Format(time.RFC3339)) 75 s.modelsLine = "models:\n - frobinator\n" 76 s.formatLine = "format: 0\n" 77 s.userPresenceLine = "user-presence: \n" 78 s.systemUserStr = strings.Replace(systemUserExample, "UNTILLINE\n", s.untilLine, 1) 79 s.systemUserStr = strings.Replace(s.systemUserStr, "SINCELINE\n", s.sinceLine, 1) 80 s.systemUserStr = strings.Replace(s.systemUserStr, "MODELSLINE\n", s.modelsLine, 1) 81 s.systemUserStr = strings.Replace(s.systemUserStr, "FORMATLINE\n", s.formatLine, 1) 82 s.systemUserStr = strings.Replace(s.systemUserStr, "USERVALIDFOR\n", s.userPresenceLine, 1) 83 } 84 85 func (s *systemUserSuite) TestDecodeOK(c *C) { 86 a, err := asserts.Decode([]byte(s.systemUserStr)) 87 c.Assert(err, IsNil) 88 c.Check(a.Type(), Equals, asserts.SystemUserType) 89 systemUser := a.(*asserts.SystemUser) 90 c.Check(systemUser.BrandID(), Equals, "canonical") 91 c.Check(systemUser.Email(), Equals, "foo@example.com") 92 c.Check(systemUser.Series(), DeepEquals, []string{"16"}) 93 c.Check(systemUser.Models(), DeepEquals, []string{"frobinator"}) 94 c.Check(systemUser.Name(), Equals, "Nice Guy") 95 c.Check(systemUser.Username(), Equals, "guy") 96 c.Check(systemUser.Password(), Equals, "$6$salt$hash") 97 c.Check(systemUser.SSHKeys(), DeepEquals, []string{"ssh-rsa AAAABcdefg"}) 98 c.Check(systemUser.Since().Equal(s.since), Equals, true) 99 c.Check(systemUser.Until().Equal(s.until), Equals, true) 100 c.Check(systemUser.UserExpiration().IsZero(), Equals, true) 101 } 102 103 func (s *systemUserSuite) TestDecodePasswd(c *C) { 104 validTests := []struct{ original, valid string }{ 105 {"password: $6$salt$hash\n", "password: $6$rounds=9999$salt$hash\n"}, 106 {"password: $6$salt$hash\n", ""}, 107 } 108 for _, test := range validTests { 109 valid := strings.Replace(s.systemUserStr, test.original, test.valid, 1) 110 _, err := asserts.Decode([]byte(valid)) 111 c.Check(err, IsNil) 112 } 113 } 114 115 func (s *systemUserSuite) TestDecodeForcePasswdChange(c *C) { 116 117 old := "password: $6$salt$hash\n" 118 new := "password: $6$salt$hash\nforce-password-change: true\n" 119 120 valid := strings.Replace(s.systemUserStr, old, new, 1) 121 a, err := asserts.Decode([]byte(valid)) 122 c.Check(err, IsNil) 123 systemUser := a.(*asserts.SystemUser) 124 c.Check(systemUser.ForcePasswordChange(), Equals, true) 125 } 126 127 func (s *systemUserSuite) TestValidAt(c *C) { 128 a, err := asserts.Decode([]byte(s.systemUserStr)) 129 c.Assert(err, IsNil) 130 su := a.(*asserts.SystemUser) 131 132 c.Check(su.ValidAt(su.Since()), Equals, true) 133 c.Check(su.ValidAt(su.Since().AddDate(0, 0, -1)), Equals, false) 134 c.Check(su.ValidAt(su.Since().AddDate(0, 0, 1)), Equals, true) 135 136 c.Check(su.ValidAt(su.Until()), Equals, false) 137 c.Check(su.ValidAt(su.Until().AddDate(0, -1, 0)), Equals, true) 138 c.Check(su.ValidAt(su.Until().AddDate(0, 1, 0)), Equals, false) 139 } 140 141 func (s *systemUserSuite) TestValidAtRevoked(c *C) { 142 // With since == until, i.e. system-user has been revoked. 143 revoked := strings.Replace(s.systemUserStr, s.sinceLine, fmt.Sprintf("since: %s\n", s.until.Format(time.RFC3339)), 1) 144 a, err := asserts.Decode([]byte(revoked)) 145 c.Assert(err, IsNil) 146 su := a.(*asserts.SystemUser) 147 148 c.Check(su.ValidAt(su.Since()), Equals, false) 149 c.Check(su.ValidAt(su.Since().AddDate(0, 0, -1)), Equals, false) 150 c.Check(su.ValidAt(su.Since().AddDate(0, 0, 1)), Equals, false) 151 152 c.Check(su.ValidAt(su.Until()), Equals, false) 153 c.Check(su.ValidAt(su.Until().AddDate(0, -1, 0)), Equals, false) 154 c.Check(su.ValidAt(su.Until().AddDate(0, 1, 0)), Equals, false) 155 } 156 157 const ( 158 systemUserErrPrefix = "assertion system-user: " 159 ) 160 161 func (s *systemUserSuite) TestDecodeInvalid(c *C) { 162 invalidTests := []struct{ original, invalid, expectedErr string }{ 163 {"brand-id: canonical\n", "", `"brand-id" header is mandatory`}, 164 {"brand-id: canonical\n", "brand-id: \n", `"brand-id" header should not be empty`}, 165 {"email: foo@example.com\n", "", `"email" header is mandatory`}, 166 {"email: foo@example.com\n", "email: \n", `"email" header should not be empty`}, 167 {"email: foo@example.com\n", "email: <alice!example.com>\n", `"email" header must be a RFC 5322 compliant email address: mail: missing @ in addr-spec`}, 168 {"email: foo@example.com\n", "email: no-mail\n", `"email" header must be a RFC 5322 compliant email address:.*`}, 169 {"series:\n - 16\n", "series: \n", `"series" header must be a list of strings`}, 170 {"series:\n - 16\n", "series: something\n", `"series" header must be a list of strings`}, 171 {"models:\n - frobinator\n", "models: \n", `"models" header must be a list of strings`}, 172 {"models:\n - frobinator\n", "models: something\n", `"models" header must be a list of strings`}, 173 {"ssh-keys:\n - ssh-rsa AAAABcdefg\n", "ssh-keys: \n", `"ssh-keys" header must be a list of strings`}, 174 {"ssh-keys:\n - ssh-rsa AAAABcdefg\n", "ssh-keys: something\n", `"ssh-keys" header must be a list of strings`}, 175 {"name: Nice Guy\n", "name:\n - foo\n", `"name" header must be a string`}, 176 {"username: guy\n", "username:\n - foo\n", `"username" header must be a string`}, 177 {"username: guy\n", "username: bäää\n", `"username" header contains invalid characters: "bäää"`}, 178 {"username: guy\n", "", `"username" header is mandatory`}, 179 {"password: $6$salt$hash\n", "password:\n - foo\n", `"password" header must be a string`}, 180 {"password: $6$salt$hash\n", "password: cleartext\n", `"password" header invalid: hashed password must be of the form "\$integer-id\$salt\$hash", see crypt\(3\)`}, 181 {"password: $6$salt$hash\n", "password: $ni!$salt$hash\n", `"password" header must start with "\$integer-id\$", got "ni!"`}, 182 {"password: $6$salt$hash\n", "password: $3$salt$hash\n", `"password" header only supports \$id\$ values of 6 \(sha512crypt\) or higher`}, 183 {"password: $6$salt$hash\n", "password: $7$invalid-salt$hash\n", `"password" header has invalid chars in salt "invalid-salt"`}, 184 {"password: $6$salt$hash\n", "password: $8$salt$invalid-hash\n", `"password" header has invalid chars in hash "invalid-hash"`}, 185 {"password: $6$salt$hash\n", "password: $8$rounds=9999$hash\n", `"password" header invalid: missing hash field`}, 186 {"password: $6$salt$hash\n", "password: $8$rounds=xxx$salt$hash\n", `"password" header has invalid number of rounds:.*`}, 187 {"password: $6$salt$hash\n", "password: $8$rounds=1$salt$hash\n", `"password" header rounds parameter out of bounds: 1`}, 188 {"password: $6$salt$hash\n", "password: $8$rounds=1999999999$salt$hash\n", `"password" header rounds parameter out of bounds: 1999999999`}, 189 {"password: $6$salt$hash\n", "force-password-change: true\n", `cannot use "force-password-change" with an empty "password"`}, 190 {"password: $6$salt$hash\n", "password: $6$salt$hash\nforce-password-change: xxx\n", `"force-password-change" header must be 'true' or 'false'`}, 191 {s.sinceLine, "since: \n", `"since" header should not be empty`}, 192 {s.sinceLine, "since: 12:30\n", `"since" header is not a RFC3339 date: .*`}, 193 {s.untilLine, "until: \n", `"until" header should not be empty`}, 194 {s.untilLine, "until: 12:30\n", `"until" header is not a RFC3339 date: .*`}, 195 {s.untilLine, "until: 1002-11-01T22:08:41+00:00\n", `'until' time cannot be before 'since' time`}, 196 {s.modelsLine, s.modelsLine + "serials: \n", `"serials" header must be a list of strings`}, 197 {s.modelsLine, s.modelsLine + "serials: something\n", `"serials" header must be a list of strings`}, 198 {s.modelsLine, s.modelsLine + "serials:\n - 7c7f435d-ed28-4281-bd77-e271e0846904\n", `the "serials" header is only supported for format 1 or greater`}, 199 {s.userPresenceLine, "user-presence: until-expiration\n", `the "user-presence" header is only supported for format 2 or greater`}, 200 } 201 202 for _, test := range invalidTests { 203 invalid := strings.Replace(s.systemUserStr, test.original, test.invalid, 1) 204 _, err := asserts.Decode([]byte(invalid)) 205 c.Check(err, ErrorMatches, systemUserErrPrefix+test.expectedErr) 206 } 207 } 208 209 func (s *systemUserSuite) TestUntilNoModels(c *C) { 210 // no models is good for <1y 211 su := strings.Replace(s.systemUserStr, s.modelsLine, "", -1) 212 _, err := asserts.Decode([]byte(su)) 213 c.Check(err, IsNil) 214 215 // but invalid for more than one year 216 oneYearPlusOne := time.Now().AddDate(1, 0, 1).Truncate(time.Second) 217 su = strings.Replace(su, s.untilLine, fmt.Sprintf("until: %s\n", oneYearPlusOne.Format(time.RFC3339)), -1) 218 _, err = asserts.Decode([]byte(su)) 219 c.Check(err, ErrorMatches, systemUserErrPrefix+"'until' time cannot be more than 365 days in the future when no models are specified") 220 } 221 222 func (s *systemUserSuite) TestUntilWithModels(c *C) { 223 // with models it can be valid forever 224 oneYearPlusOne := time.Now().AddDate(10, 0, 1).Truncate(time.Second) 225 su := strings.Replace(s.systemUserStr, s.untilLine, fmt.Sprintf("until: %s\n", oneYearPlusOne.Format(time.RFC3339)), -1) 226 _, err := asserts.Decode([]byte(su)) 227 c.Check(err, IsNil) 228 } 229 230 // The following tests deal with "format: 1" which adds support for 231 // tying system-user assertions to device serials. 232 233 var serialsLine = "serials:\n - 7c7f435d-ed28-4281-bd77-e271e0846904\n" 234 235 func (s *systemUserSuite) TestDecodeInvalidFormat1Serials(c *C) { 236 s.systemUserStr = strings.Replace(s.systemUserStr, s.formatLine, "format: 1\n", 1) 237 serialWithMultipleModels := "models:\n - m1\n - m2\n" + serialsLine 238 239 invalidTests := []struct{ original, invalid, expectedErr string }{ 240 {s.modelsLine, serialWithMultipleModels, `in the presence of the "serials" header "models" must specify exactly one model`}, 241 } 242 for _, test := range invalidTests { 243 invalid := strings.Replace(s.systemUserStr, test.original, test.invalid, 1) 244 _, err := asserts.Decode([]byte(invalid)) 245 c.Check(err, ErrorMatches, systemUserErrPrefix+test.expectedErr) 246 } 247 } 248 249 func (s *systemUserSuite) TestDecodeOKFormat1Serials(c *C) { 250 s.systemUserStr = strings.Replace(s.systemUserStr, s.formatLine, "format: 1\n", 1) 251 252 s.systemUserStr = strings.Replace(s.systemUserStr, s.modelsLine, s.modelsLine+serialsLine, 1) 253 a, err := asserts.Decode([]byte(s.systemUserStr)) 254 c.Assert(err, IsNil) 255 c.Check(a.Type(), Equals, asserts.SystemUserType) 256 systemUser := a.(*asserts.SystemUser) 257 // just for double checking, already covered by "format: 0" tests 258 c.Check(systemUser.BrandID(), Equals, "canonical") 259 // new in "format: 1" 260 c.Check(systemUser.Serials(), DeepEquals, []string{"7c7f435d-ed28-4281-bd77-e271e0846904"}) 261 262 } 263 264 func (s *systemUserSuite) TestDecodeInvalidFormat2UserPresence(c *C) { 265 s.systemUserStr = strings.Replace(s.systemUserStr, s.formatLine, "format: 2\n", 1) 266 267 invalidTests := []struct{ original, invalid, expectedErr string }{ 268 {s.userPresenceLine, "user-presence: tomorrow\n", `invalid "user-presence" header, only explicit valid value is "until-expiration": "tomorrow"`}, 269 {s.userPresenceLine, "user-presence: 0\n", `invalid "user-presence" header, only explicit valid value is "until-expiration": "0"`}, 270 } 271 for _, test := range invalidTests { 272 invalid := strings.Replace(s.systemUserStr, test.original, test.invalid, 1) 273 _, err := asserts.Decode([]byte(invalid)) 274 c.Check(err, ErrorMatches, systemUserErrPrefix+test.expectedErr) 275 } 276 } 277 278 func (s *systemUserSuite) TestDecodeOKFormat2UserPresence(c *C) { 279 s.systemUserStr = strings.Replace(s.systemUserStr, s.formatLine, "format: 2\n", 1) 280 281 s.systemUserStr = strings.Replace(s.systemUserStr, s.userPresenceLine, "user-presence: until-expiration\n", 1) 282 a, err := asserts.Decode([]byte(s.systemUserStr)) 283 c.Assert(err, IsNil) 284 c.Check(a.Type(), Equals, asserts.SystemUserType) 285 systemUser := a.(*asserts.SystemUser) 286 // new in "format: 2" 287 c.Check(systemUser.UserExpiration().Equal(systemUser.Until()), Equals, true) 288 } 289 290 func (s *systemUserSuite) TestSuggestedFormat(c *C) { 291 fmtnum, err := asserts.SuggestFormat(asserts.SystemUserType, nil, nil) 292 c.Assert(err, IsNil) 293 c.Check(fmtnum, Equals, 0) 294 295 headers := map[string]interface{}{ 296 "serials": []interface{}{"serialserial"}, 297 } 298 fmtnum, err = asserts.SuggestFormat(asserts.SystemUserType, headers, nil) 299 c.Assert(err, IsNil) 300 c.Check(fmtnum, Equals, 1) 301 302 headers = map[string]interface{}{ 303 "user-presence": "until-expiration", 304 } 305 fmtnum, err = asserts.SuggestFormat(asserts.SystemUserType, headers, nil) 306 c.Assert(err, IsNil) 307 c.Check(fmtnum, Equals, 2) 308 }