github.com/greenpau/go-authcrunch@v1.1.4/pkg/user/user_test.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package user 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "github.com/greenpau/go-authcrunch/internal/tests" 21 "github.com/greenpau/go-authcrunch/pkg/errors" 22 "testing" 23 "time" 24 ) 25 26 func TestTokenValidity(t *testing.T) { 27 testcases := []struct { 28 name string 29 data []byte 30 shouldErr bool 31 err error 32 }{ 33 { 34 name: "valid token", 35 data: []byte(fmt.Sprintf(`{"exp":%d}`, time.Now().Add(10*time.Minute).Unix())), 36 }, 37 { 38 name: "expired token", 39 data: []byte(fmt.Sprintf(`{"exp":%d}`, time.Now().Add(-10*time.Minute).Unix())), 40 shouldErr: true, 41 err: errors.ErrExpiredToken, 42 }, 43 } 44 for _, tc := range testcases { 45 t.Run(tc.name, func(t *testing.T) { 46 var msgs []string 47 msgs = append(msgs, fmt.Sprintf("test name: %s", tc.name)) 48 usr, err := NewUser(tc.data) 49 if err == nil { 50 msgs = append(msgs, fmt.Sprintf("parsed claims: %v", usr.AsMap())) 51 err = usr.Claims.Valid() 52 } 53 if tests.EvalErrWithLog(t, err, "parse token", tc.shouldErr, tc.err, msgs) { 54 return 55 } 56 wantHeaders := make(map[string]string) 57 wantHeaders["foo"] = "bar" 58 usr.SetRequestHeaders(wantHeaders) 59 gotHeaders := usr.GetRequestHeaders() 60 tests.EvalObjectsWithLog(t, "headers", wantHeaders, gotHeaders, msgs) 61 62 wantIdentity := make(map[string]interface{}) 63 wantIdentity["foo"] = "bar" 64 usr.SetRequestIdentity(wantIdentity) 65 gotIdentity := usr.GetRequestIdentity() 66 tests.EvalObjectsWithLog(t, "identity", wantIdentity, gotIdentity, msgs) 67 }) 68 } 69 } 70 71 func TestNewClaimsFromMap(t *testing.T) { 72 testcases := []struct { 73 name string 74 data interface{} 75 claims *Claims 76 err error 77 shouldErr bool 78 }{ 79 { 80 name: "valid claims with metadata mfa claims", 81 data: `{"name":"John Smith","metadata":{"mfa_required":true,"mfa_authenticated":false}}`, 82 claims: &Claims{ 83 Name: "John Smith", 84 Roles: []string{"anonymous", "guest"}, 85 Metadata: map[string]interface{}{ 86 "mfa_authenticated": false, 87 "mfa_required": true, 88 }, 89 }, 90 }, 91 { 92 name: "valid claims with email claim", 93 data: []byte(`{"email":"jsmith@contoso.com"}`), 94 claims: &Claims{ 95 Email: "jsmith@contoso.com", 96 Roles: []string{"anonymous", "guest"}, 97 }, 98 }, 99 { 100 name: "invalid email claim", 101 data: []byte(`{"email": 123456}`), 102 shouldErr: true, 103 err: errors.ErrInvalidEmailClaimType.WithArgs("email", 123456.00), 104 }, 105 { 106 name: "malformed json string", 107 data: `{"email": 123456`, 108 shouldErr: true, 109 err: fmt.Errorf("unexpected end of JSON input"), 110 }, 111 { 112 name: "user data is nil", 113 data: nil, 114 shouldErr: true, 115 err: errors.ErrInvalidUserDataType, 116 }, 117 { 118 name: "valid claims with mail claim", 119 data: []byte(`{"mail":"jsmith@contoso.com"}`), 120 claims: &Claims{ 121 Email: "jsmith@contoso.com", 122 Roles: []string{"anonymous", "guest"}, 123 }, 124 }, 125 { 126 name: "invalid mail claim", 127 data: []byte(`{"mail": 123456}`), 128 shouldErr: true, 129 err: errors.ErrInvalidEmailClaimType.WithArgs("mail", 123456.00), 130 }, 131 { 132 name: "valid claims with issuer claim", 133 data: []byte(`{"iss":"https://127.0.0.1/auth"}`), 134 claims: &Claims{ 135 Issuer: "https://127.0.0.1/auth", 136 Roles: []string{"anonymous", "guest"}, 137 }, 138 }, 139 { 140 name: "invalid issuer claim", 141 data: []byte(`{"iss": 123456}`), 142 shouldErr: true, 143 err: errors.ErrInvalidIssuerClaimType.WithArgs(123456.00), 144 }, 145 { 146 name: "valid claims with exp, iat, nbf claim in float64", 147 data: []byte(`{"exp": 1613327613.00, "nbf": 1613324013.00, "iat": 1613324013.00}`), 148 claims: &Claims{ 149 Roles: []string{"anonymous", "guest"}, 150 ExpiresAt: 1613327613, 151 IssuedAt: 1613324013, 152 NotBefore: 1613324013, 153 }, 154 }, 155 { 156 name: "valid claims with exp, iat, nbf claim in json number", 157 data: map[string]interface{}{ 158 "exp": json.Number("1613327613"), 159 "iat": json.Number("1613324013"), 160 "nbf": json.Number("1613324013"), 161 }, 162 claims: &Claims{ 163 Roles: []string{"anonymous", "guest"}, 164 ExpiresAt: 1613327613, 165 IssuedAt: 1613324013, 166 NotBefore: 1613324013, 167 }, 168 }, 169 { 170 name: "invalid exp claim", 171 data: []byte(`{"exp": "1613327613"}`), 172 shouldErr: true, 173 err: errors.ErrInvalidClaimExpiresAt.WithArgs("1613327613"), 174 }, 175 { 176 name: "invalid iat claim", 177 data: []byte(`{"iat": "1613327613"}`), 178 shouldErr: true, 179 err: errors.ErrInvalidClaimIssuedAt.WithArgs("1613327613"), 180 }, 181 { 182 name: "invalid nbf claim", 183 data: []byte(`{"nbf": "1613327613"}`), 184 shouldErr: true, 185 err: errors.ErrInvalidClaimNotBefore.WithArgs("1613327613"), 186 }, 187 { 188 name: "valid jti, sub, aud, origin, addr, and picture claims", 189 data: []byte(`{ 190 "jti": "a9d73486-b647-472a-b380-bea33a6115af", 191 "sub":"jsmith", 192 "aud":"portal", 193 "origin": "localhost", 194 "addr": "10.10.10.10", 195 "picture": "https://127.0.0.1/avatar.png" 196 }`), 197 claims: &Claims{ 198 Audience: []string{"portal"}, 199 ID: "a9d73486-b647-472a-b380-bea33a6115af", 200 Subject: "jsmith", 201 Origin: "localhost", 202 Roles: []string{"anonymous", "guest"}, 203 Address: "10.10.10.10", 204 PictureURL: "https://127.0.0.1/avatar.png", 205 }, 206 }, 207 { 208 name: "valid aud claim with multiple entries", 209 data: []byte(`{"aud":["portal","dashboard"]}`), 210 claims: &Claims{ 211 Audience: []string{"portal", "dashboard"}, 212 Roles: []string{"anonymous", "guest"}, 213 }, 214 }, 215 { 216 name: "invalid jti claim", 217 data: []byte(`{"jti": 1613327613}`), 218 shouldErr: true, 219 err: errors.ErrInvalidIDClaimType.WithArgs(1613327613.00), 220 }, 221 { 222 name: "invalid sub claim", 223 data: []byte(`{"sub": ["foo", "bar"]}`), 224 shouldErr: true, 225 err: errors.ErrInvalidSubjectClaimType.WithArgs([]interface{}{"foo", "bar"}), 226 }, 227 { 228 name: "invalid aud claim with numberic value", 229 data: []byte(`{"aud": 123456}`), 230 shouldErr: true, 231 err: errors.ErrInvalidAudienceType.WithArgs(123456.00), 232 }, 233 { 234 name: "invalid aud claim with numberic slice value", 235 data: []byte(`{"aud": [123456]}`), 236 shouldErr: true, 237 err: errors.ErrInvalidAudience.WithArgs(123456.00), 238 }, 239 { 240 name: "invalid origin claim", 241 data: []byte(`{"origin": 123456}`), 242 shouldErr: true, 243 err: errors.ErrInvalidOriginClaimType.WithArgs(123456.00), 244 }, 245 { 246 name: "valid roles claim", 247 data: []byte(`{"roles": "admin editor"}`), 248 claims: &Claims{ 249 Roles: []string{"admin", "editor"}, 250 }, 251 }, 252 { 253 name: "valid groups claim", 254 data: []byte(`{"groups":["admin","editor"]}`), 255 claims: &Claims{ 256 Roles: []string{"admin", "editor"}, 257 }, 258 }, 259 { 260 name: "invalid roles claim", 261 data: []byte(`{"roles": 123456}`), 262 shouldErr: true, 263 err: errors.ErrInvalidRoleType.WithArgs(123456.00), 264 }, 265 { 266 name: "invalid groups claim", 267 data: []byte(`{"roles":[123456, 234567]}`), 268 shouldErr: true, 269 err: errors.ErrInvalidRole.WithArgs(234567.00), 270 }, 271 { 272 name: "valid name claim with slice", 273 data: []byte(`{"name":["jsmith@contoso.com", "John Smith"]}`), 274 claims: &Claims{ 275 Name: "jsmith@contoso.com John Smith", 276 Roles: []string{"anonymous", "guest"}, 277 }, 278 }, 279 { 280 name: "valid name claim with slice and email claim with the email from name claim", 281 data: []byte(`{"name":["jsmith@contoso.com", "John Smith"],"mail":"jsmith@contoso.com"}`), 282 claims: &Claims{ 283 Name: "John Smith", 284 Email: "jsmith@contoso.com", 285 Roles: []string{"anonymous", "guest"}, 286 }, 287 }, 288 { 289 name: "invalid name claim with numeric slice", 290 data: []byte(`{"name":[123456, 234567]}`), 291 shouldErr: true, 292 err: errors.ErrInvalidNameClaimType.WithArgs([]interface{}{123456, 234567}), 293 }, 294 { 295 name: "invalid name claim with numeric value", 296 data: []byte(`{"name": 234567}`), 297 shouldErr: true, 298 err: errors.ErrInvalidNameClaimType.WithArgs(234567.00), 299 }, 300 { 301 name: "invalid addr claim", 302 data: []byte(`{"addr": 234567}`), 303 shouldErr: true, 304 err: errors.ErrInvalidAddrType.WithArgs(234567.00), 305 }, 306 { 307 name: "invalid picture claim", 308 data: []byte(`{"picture": 234567}`), 309 shouldErr: true, 310 err: errors.ErrInvalidPictureClaimType.WithArgs(234567.00), 311 }, 312 { 313 name: "invalid metadata claim", 314 data: []byte(`{"metadata": 234567}`), 315 shouldErr: true, 316 err: errors.ErrInvalidMetadataClaimType.WithArgs(234567.00), 317 }, 318 319 { 320 name: "valid app_metadata claim", 321 data: []byte(`{"app_metadata":{"authorization":{"roles":["admin", "editor"]}}}`), 322 claims: &Claims{ 323 Roles: []string{"admin", "editor"}, 324 }, 325 }, 326 { 327 name: "invalid app_metadata claim with numeric roles slice", 328 data: []byte(`{"app_metadata":{"authorization":{"roles":[123456, 234567]}}}`), 329 shouldErr: true, 330 err: errors.ErrInvalidRole.WithArgs(123456.00), 331 }, 332 { 333 name: "invalid app_metadata claim with numeric roles value", 334 data: []byte(`{"app_metadata":{"authorization":{"roles": 123456}}}`), 335 shouldErr: true, 336 err: errors.ErrInvalidAppMetadataRoleType.WithArgs(123456.00), 337 }, 338 { 339 name: "valid realm_access claim", 340 data: []byte(`{"realm_access":{"roles":["admin", "editor"]}}`), 341 claims: &Claims{ 342 Roles: []string{"admin", "editor"}, 343 }, 344 }, 345 { 346 name: "invalid realm_access claim with numeric roles slice", 347 data: []byte(`{"realm_access":{"roles":[123456, 234567]}}`), 348 shouldErr: true, 349 err: errors.ErrInvalidRole.WithArgs(123456.00), 350 }, 351 { 352 name: "valid acl claim with paths map", 353 data: []byte(`{"acl":{"paths":{"/*/users/**": {}, "/*/conversations/**": {}}}}`), 354 claims: &Claims{ 355 Roles: []string{"anonymous", "guest"}, 356 AccessList: &AccessListClaim{ 357 Paths: map[string]interface{}{ 358 "/*/conversations/**": map[string]interface{}{}, 359 "/*/users/**": map[string]interface{}{}, 360 }, 361 }, 362 }, 363 }, 364 { 365 name: "valid acl claim with paths slice", 366 data: []byte(`{"acl":{"paths":["/*/users/**", "/*/conversations/**"]}}`), 367 claims: &Claims{ 368 Roles: []string{"anonymous", "guest"}, 369 AccessList: &AccessListClaim{ 370 Paths: map[string]interface{}{ 371 "/*/conversations/**": map[string]interface{}{}, 372 "/*/users/**": map[string]interface{}{}, 373 }, 374 }, 375 }, 376 }, 377 { 378 name: "invalid acl claim with numeric paths slice", 379 data: []byte(`{"acl":{"paths":["/*/users/**", 123456]}}`), 380 shouldErr: true, 381 err: errors.ErrInvalidAccessListPath.WithArgs(123456.00), 382 }, 383 { 384 name: "valid scopes claim with string slice", 385 data: []byte(`{"scopes": ["repo", "public_repo"]}`), 386 claims: &Claims{ 387 Roles: []string{"anonymous", "guest"}, 388 Scopes: []string{"repo", "public_repo"}, 389 }, 390 }, 391 { 392 name: "valid scopes claim string value", 393 data: []byte(`{"scopes": "repo public_repo"}`), 394 claims: &Claims{ 395 Roles: []string{"anonymous", "guest"}, 396 Scopes: []string{"repo", "public_repo"}, 397 }, 398 }, 399 { 400 name: "invalid scopes claim with numeric slice", 401 data: []byte(`{"scopes": [123456]}`), 402 shouldErr: true, 403 err: errors.ErrInvalidScope.WithArgs(123456.00), 404 }, 405 { 406 name: "invalid scopes claim with numeric value", 407 data: []byte(`{"scopes": 123456}`), 408 shouldErr: true, 409 err: errors.ErrInvalidScopeType.WithArgs(123456.00), 410 }, 411 412 { 413 name: "valid paths claim", 414 data: []byte(`{"paths": ["/dropbox/jsmith/README.md"]}`), 415 claims: &Claims{ 416 Roles: []string{"anonymous", "guest"}, 417 AccessList: &AccessListClaim{ 418 Paths: map[string]interface{}{"/dropbox/jsmith/README.md": map[string]interface{}{}}, 419 }, 420 }, 421 }, 422 { 423 name: "invalid paths claim with numeric slice", 424 data: []byte(`{"paths": [123456]}`), 425 shouldErr: true, 426 err: errors.ErrInvalidAccessListPath.WithArgs(123456.00), 427 }, 428 429 { 430 name: "valid org claim with string", 431 data: []byte(`{"org": "foo bar"}`), 432 claims: &Claims{ 433 Organizations: []string{"foo", "bar"}, 434 Roles: []string{"anonymous", "guest"}, 435 }, 436 }, 437 { 438 name: "valid org claim with slice", 439 data: []byte(`{"org": ["foo","bar"]}`), 440 claims: &Claims{ 441 Organizations: []string{"foo", "bar"}, 442 Roles: []string{"anonymous", "guest"}, 443 }, 444 }, 445 { 446 name: "invalid org claim with numeric value", 447 data: []byte(`{"org": 123456}`), 448 shouldErr: true, 449 err: errors.ErrInvalidOrgType.WithArgs(123456.00), 450 }, 451 { 452 name: "invalid org claim with numeric slice", 453 data: []byte(`{"org":[123456, 234567]}`), 454 shouldErr: true, 455 err: errors.ErrInvalidOrg.WithArgs(234567.00), 456 }, 457 { 458 name: "invalid json map", 459 data: []byte(`{"org":`), 460 shouldErr: true, 461 err: fmt.Errorf("unexpected end of JSON input"), 462 }, 463 } 464 for _, tc := range testcases { 465 t.Run(tc.name, func(t *testing.T) { 466 var msgs []string 467 msgs = append(msgs, fmt.Sprintf("test name: %s", tc.name)) 468 usr, err := NewUser(tc.data) 469 if tests.EvalErrWithLog(t, err, "user map", tc.shouldErr, tc.err, msgs) { 470 return 471 } 472 msgs = append(msgs, fmt.Sprintf("parsed claims: %v", usr.AsMap())) 473 msgs = append(msgs, fmt.Sprintf("extracted key-values: %v", usr.GetData())) 474 tests.CustomEvalObjectsWithLog(t, "response", tc.claims, usr.Claims, msgs, Claims{}) 475 }) 476 } 477 }