github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/auth/auth_test.go (about) 1 // spec package is introduced to avoid circular dependencies since this 2 // particular test requires to depend on routing directly to expose the API and 3 // the APP server. 4 package auth_test 5 6 import ( 7 "encoding/base64" 8 "encoding/hex" 9 "fmt" 10 "net/http" 11 "net/url" 12 "testing" 13 "time" 14 15 "github.com/cozy/cozy-stack/model/instance" 16 "github.com/cozy/cozy-stack/model/instance/lifecycle" 17 "github.com/cozy/cozy-stack/model/oauth" 18 "github.com/cozy/cozy-stack/model/permission" 19 "github.com/cozy/cozy-stack/model/session" 20 "github.com/cozy/cozy-stack/model/stack" 21 "github.com/cozy/cozy-stack/pkg/assets/dynamic" 22 "github.com/cozy/cozy-stack/pkg/config/config" 23 "github.com/cozy/cozy-stack/pkg/consts" 24 "github.com/cozy/cozy-stack/pkg/couchdb" 25 "github.com/cozy/cozy-stack/pkg/crypto" 26 "github.com/cozy/cozy-stack/pkg/metadata" 27 "github.com/cozy/cozy-stack/tests/testutils" 28 "github.com/cozy/cozy-stack/web" 29 "github.com/cozy/cozy-stack/web/apps" 30 "github.com/cozy/cozy-stack/web/errors" 31 "github.com/cozy/cozy-stack/web/middlewares" 32 "github.com/gavv/httpexpect/v2" 33 "github.com/golang-jwt/jwt/v5" 34 "github.com/labstack/echo/v4" 35 "github.com/stretchr/testify/assert" 36 "github.com/stretchr/testify/require" 37 ) 38 39 const domain = "cozy.example.net" 40 41 func TestAuth(t *testing.T) { 42 if testing.Short() { 43 t.Skip("an instance is required for this test: test skipped due to the use of --short flag") 44 } 45 46 var JWTSecret = []byte("foobar") 47 48 var sessionID string 49 50 var clientID string 51 var clientSecret string 52 var registrationToken string 53 var altClientID string 54 var altRegistrationToken string 55 var csrfToken string 56 var code string 57 var refreshToken string 58 59 config.UseTestFile(t) 60 conf := config.GetConfig() 61 conf.Assets = "../../assets" 62 conf.DeprecatedApps = config.DeprecatedAppsCfg{ 63 Apps: []config.DeprecatedApp{ 64 { 65 SoftwareID: "github.com/some-deprecated-app", 66 Name: "some-deprecated-app", 67 StoreURLs: map[string]string{ 68 // Must test "market://" url 69 "android": "market://some-market-url", 70 "iphone": "https://some-basic-url", 71 }, 72 }, 73 }, 74 } 75 76 conf.Authentication = make(map[string]interface{}) 77 confAuth := make(map[string]interface{}) 78 confAuth["jwt_secret"] = base64.StdEncoding.EncodeToString(JWTSecret) 79 conf.Authentication[config.DefaultInstanceContext] = confAuth 80 81 _ = web.LoadSupportedLocales() 82 testutils.NeedCouchdb(t) 83 setup := testutils.NewSetup(t, t.Name()) 84 85 testInstance := setup.GetTestInstance(&lifecycle.Options{ 86 Domain: domain, 87 Email: "test@spam.cozycloud.cc", 88 Passphrase: "MyPassphrase", 89 KdfIterations: 5000, 90 Key: "xxx", 91 }) 92 93 ts := setup.GetTestServer("/test", fakeAPI, func(r *echo.Echo) *echo.Echo { 94 handler, err := web.CreateSubdomainProxy(r, &stack.Services{}, apps.Serve) 95 require.NoError(t, err, "Cant start subdomain proxy") 96 return handler 97 }) 98 ts.Config.Handler.(*echo.Echo).HTTPErrorHandler = errors.ErrorHandler 99 100 require.NoError(t, dynamic.InitDynamicAssetFS(config.FsURL().String()), "Could not init dynamic FS") 101 102 t.Run("InstanceBlocked", func(t *testing.T) { 103 e := testutils.CreateTestClient(t, ts.URL) 104 105 // Block the instance 106 testInstance.Blocked = true 107 require.NoError(t, instance.Update(testInstance)) 108 109 e.GET("/auth/login"). 110 WithHost(testInstance.Domain). 111 Expect().Status(http.StatusServiceUnavailable) 112 113 // Trying with a Accept: text/html header to simulate a browser 114 body := e.GET("/auth/login"). 115 WithHost(testInstance.Domain). 116 WithHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"). 117 Expect().Status(http.StatusServiceUnavailable). 118 Body() 119 120 body.Contains("<title>Cozy</title>") 121 body.Contains("Your Cozy has been blocked</h1>") 122 123 // Unblock the instance 124 testInstance.Blocked = false 125 _ = instance.Update(testInstance) 126 }) 127 128 t.Run("IsLoggedInWhenNotLoggedIn", func(t *testing.T) { 129 e := testutils.CreateTestClient(t, ts.URL) 130 131 e.GET("/test"). 132 WithHost(domain). 133 Expect().Status(200). 134 Text(httpexpect.ContentOpts{MediaType: "text/plain"}). 135 Equal("who_are_you") 136 }) 137 138 t.Run("HomeWhenNotLoggedIn", func(t *testing.T) { 139 e := testutils.CreateTestClient(t, ts.URL) 140 141 e.GET("/"). 142 WithHost(domain). 143 WithRedirectPolicy(httpexpect.DontFollowRedirects). 144 Expect().Status(303). 145 Header("Location").Equal("https://cozy.example.net/auth/login") 146 }) 147 148 t.Run("HomeWhenNotLoggedInWithJWT", func(t *testing.T) { 149 e := testutils.CreateTestClient(t, ts.URL) 150 151 e.GET("/").WithQuery("jwt", "foobar"). 152 WithHost(domain). 153 WithRedirectPolicy(httpexpect.DontFollowRedirects). 154 Expect().Status(303). 155 Header("Location").Equal("https://cozy.example.net/auth/login?jwt=foobar") 156 }) 157 158 t.Run("ShowLoginPage", func(t *testing.T) { 159 e := testutils.CreateTestClient(t, ts.URL) 160 161 e.GET("/auth/login"). 162 WithHost(domain). 163 Expect().Status(200). 164 Text(httpexpect.ContentOpts{MediaType: "text/html"}). 165 Contains("Log in") 166 }) 167 168 t.Run("ShowLoginPageWithRedirectBadURL", func(t *testing.T) { 169 testsRedirect := []string{" ", "foo.bar", "ftp://sub." + domain + "/foo"} 170 171 for _, test := range testsRedirect { 172 t.Run(test, func(t *testing.T) { 173 e := testutils.CreateTestClient(t, ts.URL) 174 175 e.GET("/auth/login").WithQuery("redirect", test). 176 WithHost(domain). 177 Expect().Status(400). 178 Text(httpexpect.ContentOpts{MediaType: "text/plain"}).Contains("bad url") 179 }) 180 } 181 }) 182 183 t.Run("ShowLoginPageWithRedirectXSS", func(t *testing.T) { 184 e := testutils.CreateTestClient(t, ts.URL) 185 186 e.GET("/auth/login").WithQuery("redirect", "https://sub."+domain+"/<script>alert('foo')</script>"). 187 WithHost(domain). 188 Expect().Status(200). 189 Text(httpexpect.ContentOpts{MediaType: "text/html"}). 190 Contains("%3Cscript%3Ealert%28%27foo%27%29%3C/script%3E"). 191 NotContains("<script>") 192 }) 193 194 t.Run("ShowLoginPageWithRedirectFragment", func(t *testing.T) { 195 e := testutils.CreateTestClient(t, ts.URL) 196 197 e.GET("/auth/login").WithQuery("redirect", "https://"+domain+"/auth/authorize#myfragment"). 198 WithHost(domain). 199 Expect().Status(200). 200 Text(httpexpect.ContentOpts{MediaType: "text/html"}). 201 NotContains("myfragment"). 202 Contains(`<input id="redirect" type="hidden" name="redirect" value="https://cozy.example.net/auth/authorize#=" />`) 203 }) 204 205 t.Run("ShowLoginPageWithRedirectSuccess", func(t *testing.T) { 206 e := testutils.CreateTestClient(t, ts.URL) 207 208 e.GET("/auth/login").WithQuery("redirect", "https://sub."+domain+"/foo/bar?query=foo#myfragment"). 209 WithHost(domain). 210 Expect().Status(200). 211 Text(httpexpect.ContentOpts{MediaType: "text/html"}). 212 Contains(`<input id="redirect" type="hidden" name="redirect" value="https://sub.cozy.example.net/foo/bar?query=foo#myfragment" />`) 213 }) 214 215 t.Run("LoginWithoutCSRFToken", func(t *testing.T) { 216 e := testutils.CreateTestClient(t, ts.URL) 217 218 e.POST("/auth/login").WithFormField("passphrase", "MyPassphrase"). 219 WithHost(domain). 220 Expect().Status(400) 221 }) 222 223 t.Run("LoginWithBadPassphrase", func(t *testing.T) { 224 e := testutils.CreateTestClient(t, ts.URL) 225 226 token := getLoginCSRFToken(e) 227 228 e.POST("/auth/login"). 229 WithHost(domain). 230 WithCookie("_csrf", token). 231 WithFormField("csrf_token", token). 232 WithFormField("passphrase", "Nope"). 233 Expect().Status(401) 234 }) 235 236 t.Run("LoginWithGoodPassphrase", func(t *testing.T) { 237 e := testutils.CreateTestClient(t, ts.URL) 238 239 token := getLoginCSRFToken(e) 240 241 res := e.POST("/auth/login"). 242 WithHost(domain). 243 WithRedirectPolicy(httpexpect.DontFollowRedirects). 244 WithCookie("_csrf", token). 245 WithFormField("csrf_token", token). 246 WithFormField("passphrase", "MyPassphrase"). 247 Expect().Status(303) 248 249 res.Header("Location").Equal("https://home.cozy.example.net/") 250 res.Cookies().Length().Equal(2) 251 res.Cookie("_csrf").Value().Equal(token) 252 res.Cookie(session.CookieName(testInstance)).Value().NotEmpty() 253 254 var results []*session.LoginEntry 255 err := couchdb.GetAllDocs( 256 testInstance, 257 consts.SessionsLogins, 258 &couchdb.AllDocsRequest{Limit: 100}, 259 &results, 260 ) 261 assert.NoError(t, err) 262 assert.Equal(t, 1, len(results)) 263 assert.Equal(t, "Go-http-client/1.1", results[0].UA) 264 assert.Equal(t, "127.0.0.1", results[0].IP) 265 assert.False(t, results[0].CreatedAt.IsZero()) 266 }) 267 268 t.Run("LoginWithRedirect", func(t *testing.T) { 269 t.Run("success", func(t *testing.T) { 270 e := testutils.CreateTestClient(t, ts.URL) 271 272 token := getLoginCSRFToken(e) 273 e.POST("/auth/login"). 274 WithHost(domain). 275 WithRedirectPolicy(httpexpect.DontFollowRedirects). 276 WithCookie("_csrf", token). 277 WithFormField("csrf_token", token). 278 WithFormField("passphrase", "MyPassphrase"). 279 WithFormField("redirect", "https://sub."+domain+"/#myfragment"). 280 Expect().Status(303). 281 Header("Location").Equal("https://sub.cozy.example.net/#myfragment") 282 }) 283 284 t.Run("invalid redirect field", func(t *testing.T) { 285 e := testutils.CreateTestClient(t, ts.URL) 286 287 token := getLoginCSRFToken(e) 288 e.POST("/auth/login"). 289 WithHost(domain). 290 WithRedirectPolicy(httpexpect.DontFollowRedirects). 291 WithCookie("_csrf", token). 292 WithFormField("csrf_token", token). 293 WithFormField("passphrase", "MyPassphrase"). 294 WithFormField("redirect", "foo.bar"). 295 Expect().Status(400) 296 }) 297 }) 298 299 t.Run("DelegatedJWTLoginWithRedirect", func(t *testing.T) { 300 e := testutils.CreateTestClient(t, ts.URL) 301 302 token := jwt.NewWithClaims(jwt.SigningMethodHS256, session.ExternalClaims{ 303 RegisteredClaims: jwt.RegisteredClaims{ 304 Subject: "sruti", 305 IssuedAt: jwt.NewNumericDate(time.Now()), 306 ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)), 307 }, 308 Name: domain, 309 Email: "sruti@external.notmycozy.net", 310 Code: "student", 311 }) 312 signed, err := token.SignedString(JWTSecret) 313 require.NoError(t, err) 314 315 sessionID = e.GET("/auth/login").WithQuery("jwt", signed). 316 WithHost(domain). 317 WithRedirectPolicy(httpexpect.DontFollowRedirects). 318 Expect().Status(303). 319 Cookie(session.CookieName(testInstance)).Value().NotEmpty().Raw() 320 }) 321 322 t.Run("IsLoggedInAfterLogin", func(t *testing.T) { 323 e := testutils.CreateTestClient(t, ts.URL) 324 325 e.GET("/test"). 326 WithHost(domain). 327 WithCookie(session.CookieName(testInstance), sessionID). 328 Expect().Status(200). 329 Text(httpexpect.ContentOpts{MediaType: "text/plain"}). 330 Equal("logged_in") 331 }) 332 333 t.Run("HomeWhenLoggedIn", func(t *testing.T) { 334 e := testutils.CreateTestClient(t, ts.URL) 335 336 e.GET("/"). 337 WithHost(domain). 338 WithRedirectPolicy(httpexpect.DontFollowRedirects). 339 WithCookie(session.CookieName(testInstance), sessionID). 340 Expect().Status(303). 341 Header("Location").Equal("https://home.cozy.example.net/") 342 }) 343 344 t.Run("RegisterClientNotJSON", func(t *testing.T) { 345 e := testutils.CreateTestClient(t, ts.URL) 346 347 e.POST("/auth/register"). 348 WithHost(domain). 349 WithFormField("foo", "bar"). 350 Expect().Status(400) 351 }) 352 353 t.Run("RegisterClientNoRedirectURI", func(t *testing.T) { 354 e := testutils.CreateTestClient(t, ts.URL) 355 356 obj := e.POST("/auth/register"). 357 WithHost(domain). 358 WithHeader("Accept", "application/json"). 359 WithJSON(map[string]string{ 360 "client_name": "cozy-test", 361 "software_id": "github.com/cozy/cozy-test", 362 }). 363 Expect().Status(400). 364 JSON().Object() 365 366 obj.ValueEqual("error", "invalid_redirect_uri") 367 obj.ValueEqual("error_description", "redirect_uris is mandatory") 368 }) 369 370 t.Run("RegisterClient with error", func(t *testing.T) { 371 tests := []struct { 372 name string 373 body map[string]interface{} 374 err string 375 errDescription string 376 }{ 377 { 378 name: "InvalidRedirectURI", 379 body: map[string]interface{}{ 380 "redirect_uris": []string{"http://example.org/foo#bar"}, 381 "client_name": "cozy-test", 382 "software_id": "github.com/cozy/cozy-test", 383 }, 384 err: "invalid_redirect_uri", 385 errDescription: "http://example.org/foo#bar is invalid", 386 }, 387 { 388 name: "NoClientName", 389 body: map[string]interface{}{ 390 "redirect_uris": []string{"https://example.org/oauth/callback"}, 391 "software_id": "github.com/cozy/cozy-test", 392 }, 393 err: "invalid_client_metadata", 394 errDescription: "client_name is mandatory", 395 }, 396 { 397 name: "NoSoftwareID", 398 body: map[string]interface{}{ 399 "redirect_uris": []string{"https://example.org/oauth/callback"}, 400 "client_name": "cozy-test", 401 }, 402 err: "invalid_client_metadata", 403 errDescription: "software_id is mandatory", 404 }, 405 } 406 407 for _, test := range tests { 408 t.Run(test.name, func(t *testing.T) { 409 e := testutils.CreateTestClient(t, ts.URL) 410 411 obj := e.POST("/auth/register"). 412 WithHost(domain). 413 WithHeader("Accept", "application/json"). 414 WithJSON(test.body). 415 Expect().Status(400). 416 JSON().Object() 417 418 obj.ValueEqual("error", test.err) 419 obj.ValueEqual("error_description", test.errDescription) 420 }) 421 } 422 }) 423 424 t.Run("RegisterClientSuccessWithJustMandatoryFields", func(t *testing.T) { 425 e := testutils.CreateTestClient(t, ts.URL) 426 427 obj := e.POST("/auth/register"). 428 WithHost(domain). 429 WithHeader("Accept", "application/json"). 430 WithJSON(map[string]interface{}{ 431 "redirect_uris": []string{"https://example.org/oauth/callback"}, 432 "client_name": "cozy-test", 433 "software_id": "github.com/cozy/cozy-test", 434 }). 435 Expect().Status(201). 436 JSON().Object() 437 438 obj.ValueEqual("client_secret_expires_at", 0) 439 obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"}) 440 obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"}) 441 obj.ValueEqual("response_types", []string{"code"}) 442 obj.ValueEqual("client_name", "cozy-test") 443 obj.ValueEqual("software_id", "github.com/cozy/cozy-test") 444 445 clientID = obj.Value("client_id").String().NotEmpty().NotEqual("ignored").Raw() 446 clientSecret = obj.Value("client_secret").String().NotEmpty().NotEqual("ignored").Raw() 447 registrationToken = obj.Value("registration_access_token").String().NotEmpty().NotEqual("ignored").Raw() 448 }) 449 450 t.Run("RegisterClientSuccessWithAllFields", func(t *testing.T) { 451 e := testutils.CreateTestClient(t, ts.URL) 452 453 obj := e.POST("/auth/register"). 454 WithHost(domain). 455 WithHeader("Accept", "application/json"). 456 WithJSON(map[string]interface{}{ 457 "_id": "ignored", 458 "_rev": "ignored", 459 "client_id": "ignored", 460 "client_secret": "ignored", 461 "client_secret_expires_at": 42, 462 "registration_access_token": "ignored", 463 "redirect_uris": []string{"https://example.org/oauth/callback"}, 464 "grant_types": []string{"ignored"}, 465 "response_types": []string{"ignored"}, 466 "client_name": "new-cozy-test", 467 "client_kind": "test", 468 "client_uri": "https://github.com/cozy/cozy-test", 469 "logo_uri": "https://raw.github.com/cozy/cozy-setup/gh-pages/assets/images/happycloud.png", 470 "policy_uri": "https://github/com/cozy/cozy-test/master/policy.md", 471 "software_id": "github.com/cozy/cozy-test", 472 "software_version": "v0.1.2", 473 }). 474 Expect().Status(201). 475 JSON().Object() 476 477 obj.NotContainsKey("_id") 478 obj.NotContainsKey("_rev") 479 obj.Value("registration_access_token").String().NotEmpty().NotEqual("ignored").Raw() 480 481 obj.ValueEqual("client_secret_expires_at", 0) 482 obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"}) 483 obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"}) 484 obj.ValueEqual("response_types", []string{"code"}) 485 obj.ValueEqual("client_name", "new-cozy-test") 486 obj.ValueEqual("client_kind", "test") 487 obj.ValueEqual("client_uri", "https://github.com/cozy/cozy-test") 488 obj.ValueEqual("logo_uri", "https://raw.github.com/cozy/cozy-setup/gh-pages/assets/images/happycloud.png") 489 obj.ValueEqual("policy_uri", "https://github/com/cozy/cozy-test/master/policy.md") 490 obj.ValueEqual("software_id", "github.com/cozy/cozy-test") 491 obj.ValueEqual("software_version", "v0.1.2") 492 493 altClientID = obj.Value("client_id").String().NotEmpty().NotEqual("ignored").Raw() 494 altRegistrationToken = obj.Value("registration_access_token").String().NotEmpty().NotEqual("ignored").Raw() 495 }) 496 497 t.Run("RegisterSharingClientSuccess", func(t *testing.T) { 498 e := testutils.CreateTestClient(t, ts.URL) 499 500 obj := e.POST("/auth/register"). 501 WithHost(domain). 502 WithHeader("Accept", "application/json"). 503 WithJSON(map[string]interface{}{ 504 "redirect_uris": []string{"https://cozy.example.org/sharings/answer"}, 505 "client_name": "John", 506 "software_id": "github.com/cozy/cozy-stack", 507 "client_kind": "sharing", 508 "client_uri": "https://cozy.example.org", 509 }). 510 Expect().Status(201). 511 JSON().Object() 512 513 obj.Value("client_id").String().NotEmpty().NotEqual("ignored").Raw() 514 obj.Value("client_secret").String().NotEmpty().NotEqual("ignored").Raw() 515 obj.Value("registration_access_token").String().NotEmpty().NotEqual("ignored").Raw() 516 517 obj.ValueEqual("client_secret_expires_at", 0) 518 obj.ValueEqual("redirect_uris", []string{"https://cozy.example.org/sharings/answer"}) 519 obj.ValueEqual("client_name", "John") 520 obj.ValueEqual("software_id", "github.com/cozy/cozy-stack") 521 }) 522 523 t.Run("DeleteClientNoToken", func(t *testing.T) { 524 e := testutils.CreateTestClient(t, ts.URL) 525 526 e.DELETE("/auth/register/" + altClientID). 527 WithHost(domain). 528 Expect().Status(401) 529 }) 530 531 t.Run("DeleteClientSuccess", func(t *testing.T) { 532 e := testutils.CreateTestClient(t, ts.URL) 533 534 e.DELETE("/auth/register/"+altClientID). 535 WithHost(domain). 536 WithHeader("Authorization", "Bearer "+altRegistrationToken). 537 Expect().Status(204) 538 539 // And next calls should return a 204 too 540 e.DELETE("/auth/register/"+altClientID). 541 WithHost(domain). 542 WithHeader("Authorization", "Bearer "+altRegistrationToken). 543 Expect().Status(204) 544 }) 545 546 t.Run("ReadClientNoToken", func(t *testing.T) { 547 e := testutils.CreateTestClient(t, ts.URL) 548 549 e.GET("/auth/register/"+clientID). 550 WithHost(domain). 551 WithHeader("Accept", "application/json"). 552 Expect().Status(401). 553 Body().NotContains(clientSecret) 554 }) 555 556 t.Run("ReadClientInvalidToken", func(t *testing.T) { 557 e := testutils.CreateTestClient(t, ts.URL) 558 559 e.GET("/auth/register/"+clientID). 560 WithHost(domain). 561 WithHeader("Accept", "application/json"). 562 WithHeader("Authorization", "Bearer "+altRegistrationToken). 563 Expect().Status(401). 564 Body().NotContains(clientSecret) 565 }) 566 567 t.Run("DeprecatedApp", func(t *testing.T) { 568 e := testutils.CreateTestClient(t, ts.URL) 569 570 deprecatedClientID := e.POST("/auth/register"). 571 WithHost(domain). 572 WithHeader("Accept", "application/json"). 573 WithJSON(map[string]interface{}{ 574 "redirect_uris": []string{"https://example.org/oauth/callback"}, 575 "client_name": "cozy-test", 576 "software_id": "github.com/some-deprecated-app", 577 }). 578 Expect().Status(201). 579 JSON().Object(). 580 Value("client_id").String().NotEmpty().NotEqual("ignored").Raw() 581 582 body := e.GET("/auth/authorize"). 583 WithHeader("User-Agent", "Mozilla/5.0 (Linux; U; Android 1.5; de-; HTC Magic Build/PLAT-RC33) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1"). 584 WithQuery("response_type", "code"). 585 WithQuery("state", "123456"). 586 WithQuery("scope", "files:read"). 587 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 588 WithQuery("client_id", deprecatedClientID). 589 WithHost(domain). 590 WithRedirectPolicy(httpexpect.DontFollowRedirects). 591 Expect().Status(200). 592 Body() 593 594 body.Contains(`href="market://some-market-url"`) 595 }) 596 597 t.Run("ReadClientInvalidClientID", func(t *testing.T) { 598 e := testutils.CreateTestClient(t, ts.URL) 599 600 e.GET("/auth/register/"+altClientID). 601 WithHost(domain). 602 WithHeader("Accept", "application/json"). 603 WithHeader("Authorization", "Bearer "+altRegistrationToken). 604 Expect().Status(404) 605 }) 606 607 t.Run("ReadClientSuccess", func(t *testing.T) { 608 e := testutils.CreateTestClient(t, ts.URL) 609 610 obj := e.GET("/auth/register/"+clientID). 611 WithHost(domain). 612 WithHeader("Accept", "application/json"). 613 WithHeader("Authorization", "Bearer "+registrationToken). 614 Expect().Status(200). 615 JSON().Object() 616 617 obj.ValueEqual("client_id", clientID) 618 obj.ValueEqual("client_secret", clientSecret) 619 obj.ValueEqual("client_secret_expires_at", 0) 620 obj.NotContainsKey("registration_access_token") 621 obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"}) 622 obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"}) 623 obj.ValueEqual("response_types", []string{"code"}) 624 obj.ValueEqual("client_name", "cozy-test") 625 obj.ValueEqual("software_id", "github.com/cozy/cozy-test") 626 }) 627 628 t.Run("UpdateClientDeletedClientID", func(t *testing.T) { 629 e := testutils.CreateTestClient(t, ts.URL) 630 631 e.PUT("/auth/register/"+altClientID). 632 WithHost(domain). 633 WithHeader("Accept", "application/json"). 634 WithHeader("Authorization", "Bearer "+registrationToken). 635 WithJSON(map[string]string{ 636 "client_id": altClientID, 637 }). 638 Expect().Status(404) 639 }) 640 641 t.Run("UpdateClientInvalidClientID", func(t *testing.T) { 642 e := testutils.CreateTestClient(t, ts.URL) 643 644 obj := e.PUT("/auth/register/"+clientID). 645 WithHost(domain). 646 WithHeader("Accept", "application/json"). 647 WithHeader("Authorization", "Bearer "+registrationToken). 648 WithJSON(map[string]string{ 649 "client_id": "123456789", 650 }). 651 Expect().Status(400). 652 JSON().Object() 653 654 obj.ValueEqual("error", "invalid_client_id") 655 obj.ValueEqual("error_description", "client_id is mandatory") 656 }) 657 658 t.Run("UpdateClientNoRedirectURI", func(t *testing.T) { 659 e := testutils.CreateTestClient(t, ts.URL) 660 661 obj := e.PUT("/auth/register/"+clientID). 662 WithHost(domain). 663 WithHeader("Accept", "application/json"). 664 WithHeader("Authorization", "Bearer "+registrationToken). 665 WithJSON(map[string]string{ 666 "client_id": clientID, 667 "client_name": "cozy-test", 668 "software_id": "github.com/cozy/cozy-test", 669 }). 670 Expect().Status(400). 671 JSON().Object() 672 673 obj.ValueEqual("error", "invalid_redirect_uri") 674 obj.ValueEqual("error_description", "redirect_uris is mandatory") 675 }) 676 677 t.Run("UpdateClientSuccess", func(t *testing.T) { 678 e := testutils.CreateTestClient(t, ts.URL) 679 680 obj := e.PUT("/auth/register/"+clientID). 681 WithHost(domain). 682 WithHeader("Accept", "application/json"). 683 WithHeader("Authorization", "Bearer "+registrationToken). 684 WithJSON(map[string]interface{}{ 685 "client_id": clientID, 686 "client_name": "cozy-test-new-name", 687 "redirect_uris": []string{"https://example.org/oauth/callback"}, 688 "software_id": "github.com/cozy/cozy-test", 689 "software_version": "v0.1.3", 690 }). 691 Expect().Status(200). 692 JSON().Object() 693 694 obj.ValueEqual("client_id", clientID) 695 obj.ValueEqual("client_secret", clientSecret) 696 obj.ValueEqual("client_secret_expires_at", 0) 697 obj.NotContainsKey("registration_access_token") 698 obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"}) 699 obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"}) 700 obj.ValueEqual("response_types", []string{"code"}) 701 obj.ValueEqual("client_name", "cozy-test-new-name") 702 obj.ValueEqual("software_id", "github.com/cozy/cozy-test") 703 obj.ValueEqual("software_version", "v0.1.3") 704 }) 705 706 t.Run("UpdateClientSecret", func(t *testing.T) { 707 e := testutils.CreateTestClient(t, ts.URL) 708 709 obj := e.PUT("/auth/register/"+clientID). 710 WithHost(domain). 711 WithHeader("Accept", "application/json"). 712 WithHeader("Authorization", "Bearer "+registrationToken). 713 WithJSON(map[string]interface{}{ 714 "client_id": clientID, 715 "client_secret": clientSecret, 716 "redirect_uris": []string{"https://example.org/oauth/callback"}, 717 "client_name": "cozy-test", 718 "software_id": "github.com/cozy/cozy-test", 719 "software_version": "v0.1.4", 720 }). 721 Expect().Status(200). 722 JSON().Object() 723 724 clientSecret = obj.Value("client_secret").String().NotEqual(clientSecret).Raw() 725 726 obj.ValueEqual("client_id", clientID) 727 obj.ValueEqual("client_secret_expires_at", 0) 728 obj.NotContainsKey("registration_access_token") 729 obj.ValueEqual("redirect_uris", []string{"https://example.org/oauth/callback"}) 730 obj.ValueEqual("grant_types", []string{"authorization_code", "refresh_token"}) 731 obj.ValueEqual("response_types", []string{"code"}) 732 obj.ValueEqual("client_name", "cozy-test") 733 obj.ValueEqual("software_id", "github.com/cozy/cozy-test") 734 obj.ValueEqual("software_version", "v0.1.4") 735 }) 736 737 t.Run("AuthorizeFormRedirectsWhenNotLoggedIn", func(t *testing.T) { 738 e := testutils.CreateTestClient(t, ts.URL) 739 740 e.GET("/auth/authorize"). 741 WithQuery("response_type", "code"). 742 WithQuery("state", "123456"). 743 WithQuery("scope", "files:read"). 744 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 745 WithQuery("client_id", clientID). 746 WithHost(domain). 747 WithRedirectPolicy(httpexpect.DontFollowRedirects). 748 Expect().Status(303) 749 }) 750 751 t.Run("AuthorizeFormBadResponseType", func(t *testing.T) { 752 e := testutils.CreateTestClient(t, ts.URL) 753 754 e.GET("/auth/authorize"). 755 WithQuery("response_type", "token"). // invalid 756 WithQuery("state", "123456"). 757 WithQuery("scope", "files:read"). 758 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 759 WithQuery("client_id", clientID). 760 WithHost(domain). 761 Expect().Status(400). 762 ContentType("text/html", "utf-8"). 763 Body().Contains("Invalid response type") 764 }) 765 766 t.Run("AuthorizeFormNoState", func(t *testing.T) { 767 e := testutils.CreateTestClient(t, ts.URL) 768 769 e.GET("/auth/authorize"). 770 WithQuery("response_type", "code"). 771 WithQuery("scope", "files:read"). 772 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 773 WithQuery("client_id", clientID). 774 WithHost(domain). 775 Expect().Status(400). 776 ContentType("text/html", "utf-8"). 777 Body().Contains("The state parameter is mandatory") 778 }) 779 780 t.Run("AuthorizeFormNoClientId", func(t *testing.T) { 781 e := testutils.CreateTestClient(t, ts.URL) 782 783 e.GET("/auth/authorize"). 784 WithQuery("response_type", "code"). 785 WithQuery("state", "123456"). 786 WithQuery("scope", "files:read"). 787 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 788 WithHost(domain). 789 Expect().Status(400). 790 ContentType("text/html", "utf-8"). 791 Body().Contains("The client_id parameter is mandatory") 792 }) 793 794 t.Run("AuthorizeFormNoRedirectURI", func(t *testing.T) { 795 e := testutils.CreateTestClient(t, ts.URL) 796 797 e.GET("/auth/authorize"). 798 WithQuery("response_type", "code"). 799 WithQuery("state", "123456"). 800 WithQuery("scope", "files:read"). 801 WithQuery("client_id", clientID). 802 WithHost(domain). 803 Expect().Status(400). 804 ContentType("text/html", "utf-8"). 805 Body().Contains("The redirect_uri parameter is mandatory") 806 }) 807 808 t.Run("AuthorizeFormNoScope", func(t *testing.T) { 809 e := testutils.CreateTestClient(t, ts.URL) 810 811 e.GET("/auth/authorize"). 812 WithQuery("response_type", "code"). 813 WithQuery("state", "123456"). 814 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 815 WithQuery("client_id", clientID). 816 WithHost(domain). 817 Expect().Status(400). 818 ContentType("text/html", "utf-8"). 819 Body().Contains("The scope parameter is mandatory") 820 }) 821 822 t.Run("AuthorizeFormInvalidClient", func(t *testing.T) { 823 e := testutils.CreateTestClient(t, ts.URL) 824 825 e.GET("/auth/authorize"). 826 WithQuery("response_type", "code"). 827 WithQuery("state", "123456"). 828 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 829 WithQuery("scope", "files:read"). 830 WithQuery("client_id", "foo"). 831 WithHost(domain). 832 Expect().Status(400). 833 ContentType("text/html", "utf-8"). 834 Body().Contains("The client must be registered") 835 }) 836 837 t.Run("AuthorizeFormInvalidRedirectURI", func(t *testing.T) { 838 e := testutils.CreateTestClient(t, ts.URL) 839 840 e.GET("/auth/authorize"). 841 WithQuery("response_type", "code"). 842 WithQuery("state", "123456"). 843 WithQuery("redirect_uri", "https://evil.com/"). 844 WithQuery("scope", "files:read"). 845 WithQuery("client_id", clientID). 846 WithHost(domain). 847 Expect().Status(400). 848 ContentType("text/html", "utf-8"). 849 Body().Contains("The redirect_uri parameter doesn't match the registered ones") 850 }) 851 852 t.Run("AuthorizeFormSuccess", func(t *testing.T) { 853 e := testutils.CreateTestClient(t, ts.URL) 854 855 resBody := e.GET("/auth/authorize"). 856 WithQuery("response_type", "code"). 857 WithQuery("state", "123456"). 858 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 859 WithQuery("scope", "files:read"). 860 WithQuery("client_id", clientID). 861 WithCookie(session.CookieName(testInstance), sessionID). 862 WithHost(domain). 863 WithRedirectPolicy(httpexpect.DontFollowRedirects). 864 Expect().Status(200). 865 ContentType("text/html", "utf-8"). 866 Body() 867 868 resBody.Contains("would like permission to access your Cozy") 869 matches := resBody.Match(`<input type="hidden" name="csrf_token" value="(\w+)"`) 870 matches.Length().Equal(2) 871 csrfToken = matches.Index(1).Raw() 872 }) 873 874 t.Run("AuthorizeFormClientMobileApp", func(t *testing.T) { 875 e := testutils.CreateTestClient(t, ts.URL) 876 877 var oauthClient oauth.Client 878 879 u := "https://example.org/oauth/callback" 880 oauthClient.RedirectURIs = []string{u} 881 oauthClient.ClientName = "cozy-test-2" 882 oauthClient.SoftwareID = "registry://drive" 883 oauthClient.Create(testInstance) 884 885 e.GET("/auth/authorize"). 886 WithQuery("response_type", "code"). 887 WithQuery("state", "123456"). 888 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 889 WithQuery("client_id", oauthClient.ClientID). 890 WithCookie(session.CookieName(testInstance), sessionID). 891 WithHost(domain). 892 Expect().Status(200). 893 ContentType("text/html", "utf-8"). 894 Body(). 895 Contains("io.cozy.files") 896 }) 897 898 t.Run("AuthorizeFormFlagshipApp", func(t *testing.T) { 899 e := testutils.CreateTestClient(t, ts.URL) 900 901 var oauthClient oauth.Client 902 903 u := "https://example.org/oauth/callback" 904 oauthClient.RedirectURIs = []string{u} 905 oauthClient.ClientName = "cozy-test-2" 906 oauthClient.SoftwareID = "registry://drive" 907 oauthClient.Create(testInstance) 908 909 e.GET("/auth/authorize"). 910 WithQuery("response_type", "code"). 911 WithQuery("state", "123456"). 912 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 913 WithQuery("client_id", clientID). 914 WithQuery("scope", "*"). 915 WithQuery("code_challenge", "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI"). 916 WithQuery("code_challenge_method", "S256"). 917 WithCookie(session.CookieName(testInstance), sessionID). 918 WithHost(domain). 919 Expect().Status(200). 920 ContentType("text/html", "utf-8"). 921 Body(). 922 NotContains("would like permission to access your Cozy"). 923 Contains("The origin of this application is not certified.") 924 }) 925 926 t.Run("AuthorizeWhenNotLoggedIn", func(t *testing.T) { 927 e := testutils.CreateTestClient(t, ts.URL) 928 929 e.POST("/auth/authorize"). 930 WithFormField("state", "123456"). 931 WithFormField("client_id", clientID). 932 WithFormField("redirect_uri", "https://example.org/oauth/callback"). 933 WithFormField("scope", "files:read"). 934 WithFormField("csrf_token", csrfToken). 935 WithFormField("response_type", "code"). 936 WithHost(domain). 937 Expect().Status(403) 938 }) 939 940 t.Run("AuthorizeWithInvalidCSRFToken", func(t *testing.T) { 941 e := testutils.CreateTestClient(t, ts.URL) 942 943 e.POST("/auth/authorize"). 944 WithFormField("state", "123456"). 945 WithFormField("client_id", clientID). 946 WithFormField("redirect_uri", "https://example.org/oauth/callback"). 947 WithFormField("scope", "files:read"). 948 WithFormField("csrf_token", "azertyuiop"). 949 WithFormField("response_type", "code"). 950 WithHost(domain). 951 WithCookie("_csrf", csrfToken). 952 Expect().Status(403). 953 Text(httpexpect.ContentOpts{MediaType: "text/plain"}). 954 Contains("invalid csrf token") 955 }) 956 957 t.Run("AuthorizeWithNoState", func(t *testing.T) { 958 e := testutils.CreateTestClient(t, ts.URL) 959 960 e.POST("/auth/authorize"). 961 WithFormField("client_id", clientID). 962 WithFormField("redirect_uri", "https://example.org/oauth/callback"). 963 WithFormField("scope", "files:read"). 964 WithFormField("csrf_token", csrfToken). 965 WithFormField("response_type", "code"). 966 WithHost(domain). 967 WithCookie("_csrf", csrfToken). 968 Expect().Status(400). 969 ContentType("text/html", "utf-8"). 970 Body().Contains("The state parameter is mandatory") 971 }) 972 973 t.Run("AuthorizeWithNoClientID", func(t *testing.T) { 974 e := testutils.CreateTestClient(t, ts.URL) 975 976 e.POST("/auth/authorize"). 977 WithFormField("state", "123456"). 978 WithFormField("redirect_uri", "https://example.org/oauth/callback"). 979 WithFormField("scope", "files:read"). 980 WithFormField("csrf_token", csrfToken). 981 WithFormField("response_type", "code"). 982 WithHost(domain). 983 WithCookie("_csrf", csrfToken). 984 Expect().Status(400). 985 ContentType("text/html", "utf-8"). 986 Body().Contains("The client_id parameter is mandatory") 987 }) 988 989 t.Run("AuthorizeWithInvalidClientID", func(t *testing.T) { 990 e := testutils.CreateTestClient(t, ts.URL) 991 992 e.POST("/auth/authorize"). 993 WithFormField("state", "123456"). 994 WithFormField("client_id", "invalid"). 995 WithFormField("redirect_uri", "https://example.org/oauth/callback"). 996 WithFormField("scope", "files:read"). 997 WithFormField("csrf_token", csrfToken). 998 WithFormField("response_type", "code"). 999 WithHost(domain). 1000 WithCookie("_csrf", csrfToken). 1001 Expect().Status(400). 1002 ContentType("text/html", "utf-8"). 1003 Body().Contains("The client must be registered") 1004 }) 1005 1006 t.Run("AuthorizeWithNoRedirectURI", func(t *testing.T) { 1007 e := testutils.CreateTestClient(t, ts.URL) 1008 1009 e.POST("/auth/authorize"). 1010 WithFormField("state", "123456"). 1011 WithFormField("client_id", clientID). 1012 WithFormField("scope", "files:read"). 1013 WithFormField("csrf_token", csrfToken). 1014 WithFormField("response_type", "code"). 1015 WithHost(domain). 1016 WithCookie("_csrf", csrfToken). 1017 Expect().Status(400). 1018 ContentType("text/html", "utf-8"). 1019 Body().Contains("The redirect_uri parameter is mandatory") 1020 }) 1021 1022 t.Run("AuthorizeWithInvalidURI", func(t *testing.T) { 1023 e := testutils.CreateTestClient(t, ts.URL) 1024 1025 e.POST("/auth/authorize"). 1026 WithFormField("state", "123456"). 1027 WithFormField("client_id", clientID). 1028 WithFormField("scope", "files:read"). 1029 WithFormField("redirect_uri", "/oauth/callback"). 1030 WithFormField("csrf_token", csrfToken). 1031 WithFormField("response_type", "code"). 1032 WithHost(domain). 1033 WithCookie("_csrf", csrfToken). 1034 Expect().Status(400). 1035 ContentType("text/html", "utf-8"). 1036 Body().Contains("The redirect_uri parameter doesn't match the registered ones") 1037 }) 1038 1039 t.Run("AuthorizeWithNoScope", func(t *testing.T) { 1040 e := testutils.CreateTestClient(t, ts.URL) 1041 1042 e.POST("/auth/authorize"). 1043 WithFormField("state", "123456"). 1044 WithFormField("client_id", clientID). 1045 WithFormField("redirect_uri", "https://example.org/oauth/callback"). 1046 WithFormField("csrf_token", csrfToken). 1047 WithFormField("response_type", "code"). 1048 WithHost(domain). 1049 WithCookie("_csrf", csrfToken). 1050 Expect().Status(400). 1051 ContentType("text/html", "utf-8"). 1052 Body().Contains("The scope parameter is mandatory") 1053 }) 1054 1055 t.Run("AuthorizeSuccess", func(t *testing.T) { 1056 e := testutils.CreateTestClient(t, ts.URL) 1057 1058 redirectURL := e.POST("/auth/authorize"). 1059 WithFormField("state", "123456"). 1060 WithFormField("client_id", clientID). 1061 WithFormField("scope", "files:read"). 1062 WithFormField("redirect_uri", "https://example.org/oauth/callback"). 1063 WithFormField("csrf_token", csrfToken). 1064 WithFormField("response_type", "code"). 1065 WithHost(domain). 1066 WithCookie("_csrf", csrfToken). 1067 WithCookie(session.CookieName(testInstance), sessionID). 1068 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1069 Expect().Status(302). 1070 Header("Location") 1071 1072 var results []oauth.AccessCode 1073 req := &couchdb.AllDocsRequest{} 1074 err := couchdb.GetAllDocs(testInstance, consts.OAuthAccessCodes, req, &results) 1075 require.NoError(t, err) 1076 require.Len(t, results, 1) 1077 1078 code = results[0].Code 1079 redirectURL.Equal(fmt.Sprintf("https://example.org/oauth/callback?access_code=%s&code=%s&state=123456#", code, code)) 1080 assert.Equal(t, results[0].ClientID, clientID) 1081 assert.Equal(t, results[0].Scope, "files:read") 1082 }) 1083 1084 t.Run("AccessTokenNoGrantType", func(t *testing.T) { 1085 e := testutils.CreateTestClient(t, ts.URL) 1086 1087 e.POST("/auth/access_token"). 1088 WithFormField("client_id", clientID). 1089 WithFormField("client_secret", clientSecret). 1090 WithFormField("code", code). 1091 WithHost(domain). 1092 Expect().Status(400). 1093 JSON().Object(). 1094 ValueEqual("error", "the grant_type parameter is mandatory") 1095 }) 1096 1097 t.Run("AccessTokenInvalidGrantType", func(t *testing.T) { 1098 e := testutils.CreateTestClient(t, ts.URL) 1099 1100 e.POST("/auth/access_token"). 1101 WithFormField("grant_type", "token"). // invalide 1102 WithFormField("client_id", clientID). 1103 WithFormField("client_secret", clientSecret). 1104 WithFormField("code", code). 1105 WithHost(domain). 1106 Expect().Status(400). 1107 JSON().Object(). 1108 ValueEqual("error", "invalid grant type") 1109 }) 1110 1111 t.Run("AccessTokenNoClientID", func(t *testing.T) { 1112 e := testutils.CreateTestClient(t, ts.URL) 1113 1114 e.POST("/auth/access_token"). 1115 WithFormField("grant_type", "authorization_code"). 1116 WithFormField("client_secret", clientSecret). 1117 WithFormField("code", code). 1118 WithHost(domain). 1119 Expect().Status(400). 1120 JSON().Object(). 1121 ValueEqual("error", "the client_id parameter is mandatory") 1122 }) 1123 1124 t.Run("AccessTokenInvalidClientID", func(t *testing.T) { 1125 e := testutils.CreateTestClient(t, ts.URL) 1126 1127 e.POST("/auth/access_token"). 1128 WithFormField("grant_type", "authorization_code"). 1129 WithFormField("client_id", "foo"). // invalid 1130 WithFormField("client_secret", clientSecret). 1131 WithFormField("code", code). 1132 WithHost(domain). 1133 Expect().Status(400). 1134 JSON().Object(). 1135 ValueEqual("error", "the client must be registered") 1136 }) 1137 1138 t.Run("AccessTokenNoClientSecret", func(t *testing.T) { 1139 e := testutils.CreateTestClient(t, ts.URL) 1140 1141 e.POST("/auth/access_token"). 1142 WithFormField("grant_type", "authorization_code"). 1143 WithFormField("client_id", clientID). 1144 WithFormField("code", code). 1145 WithHost(domain). 1146 Expect().Status(400). 1147 JSON().Object(). 1148 ValueEqual("error", "the client_secret parameter is mandatory") 1149 }) 1150 1151 t.Run("AccessTokenInvalidClientSecret", func(t *testing.T) { 1152 e := testutils.CreateTestClient(t, ts.URL) 1153 1154 e.POST("/auth/access_token"). 1155 WithFormField("grant_type", "authorization_code"). 1156 WithFormField("client_id", clientID). 1157 WithFormField("client_secret", "foo"). // invalid 1158 WithFormField("code", code). 1159 WithHost(domain). 1160 Expect().Status(400). 1161 JSON().Object(). 1162 ValueEqual("error", "invalid client_secret") 1163 }) 1164 1165 t.Run("AccessTokenNoCode", func(t *testing.T) { 1166 e := testutils.CreateTestClient(t, ts.URL) 1167 1168 e.POST("/auth/access_token"). 1169 WithFormField("grant_type", "authorization_code"). 1170 WithFormField("client_id", clientID). 1171 WithFormField("client_secret", clientSecret). 1172 WithHost(domain). 1173 Expect().Status(400). 1174 JSON().Object(). 1175 ValueEqual("error", "the code parameter is mandatory") 1176 }) 1177 1178 t.Run("AccessTokenInvalidCode", func(t *testing.T) { 1179 e := testutils.CreateTestClient(t, ts.URL) 1180 1181 e.POST("/auth/access_token"). 1182 WithFormField("grant_type", "authorization_code"). 1183 WithFormField("client_id", clientID). 1184 WithFormField("client_secret", clientSecret). 1185 WithFormField("code", "foo"). 1186 WithHost(domain). 1187 Expect().Status(400). 1188 JSON().Object(). 1189 ValueEqual("error", "invalid code") 1190 }) 1191 1192 t.Run("AccessTokenSuccess", func(t *testing.T) { 1193 e := testutils.CreateTestClient(t, ts.URL) 1194 1195 obj := e.POST("/auth/access_token"). 1196 WithFormField("grant_type", "authorization_code"). 1197 WithFormField("client_id", clientID). 1198 WithFormField("client_secret", clientSecret). 1199 WithFormField("code", code). 1200 WithHost(domain). 1201 Expect().Status(200). 1202 JSON().Object() 1203 1204 obj.ValueEqual("token_type", "bearer") 1205 obj.ValueEqual("scope", "files:read") 1206 1207 assertValidToken(t, testInstance, obj.Value("access_token").String().Raw(), "access", clientID, "files:read") 1208 assertValidToken(t, testInstance, obj.Value("refresh_token").String().Raw(), "refresh", clientID, "files:read") 1209 1210 refreshToken = obj.Value("refresh_token").String().NotEmpty().Raw() 1211 }) 1212 1213 t.Run("RefreshTokenNoToken", func(t *testing.T) { 1214 e := testutils.CreateTestClient(t, ts.URL) 1215 1216 e.POST("/auth/access_token"). 1217 WithFormField("grant_type", "refresh_token"). 1218 WithFormField("client_id", clientID). 1219 WithFormField("client_secret", clientSecret). 1220 WithHost(domain). 1221 Expect().Status(400). 1222 JSON().Object(). 1223 ValueEqual("error", "invalid refresh token") 1224 }) 1225 1226 t.Run("RefreshTokenInvalidToken", func(t *testing.T) { 1227 e := testutils.CreateTestClient(t, ts.URL) 1228 1229 e.POST("/auth/access_token"). 1230 WithFormField("grant_type", "refresh_token"). 1231 WithFormField("client_id", clientID). 1232 WithFormField("client_secret", clientSecret). 1233 WithFormField("refresh_token", "foo"). 1234 WithHost(domain). 1235 Expect().Status(400). 1236 JSON().Object(). 1237 ValueEqual("error", "invalid refresh token") 1238 }) 1239 1240 t.Run("RefreshTokenInvalidSigningMethod", func(t *testing.T) { 1241 e := testutils.CreateTestClient(t, ts.URL) 1242 1243 claims := permission.Claims{ 1244 RegisteredClaims: jwt.RegisteredClaims{ 1245 Audience: jwt.ClaimStrings{consts.RefreshTokenAudience}, 1246 Issuer: domain, 1247 IssuedAt: jwt.NewNumericDate(time.Now()), 1248 Subject: clientID, 1249 }, 1250 Scope: "files:write", 1251 } 1252 token := jwt.NewWithClaims(jwt.GetSigningMethod("none"), claims) 1253 fakeToken, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType) 1254 require.NoError(t, err) 1255 1256 e.POST("/auth/access_token"). 1257 WithFormField("grant_type", "refresh_token"). 1258 WithFormField("client_id", clientID). 1259 WithFormField("client_secret", clientSecret). 1260 WithFormField("refresh_token", fakeToken). 1261 WithHost(domain). 1262 Expect().Status(400). 1263 JSON().Object(). 1264 ValueEqual("error", "invalid refresh token") 1265 }) 1266 1267 t.Run("RefreshTokenSuccess", func(t *testing.T) { 1268 e := testutils.CreateTestClient(t, ts.URL) 1269 1270 obj := e.POST("/auth/access_token"). 1271 WithFormField("grant_type", "refresh_token"). 1272 WithFormField("client_id", clientID). 1273 WithFormField("client_secret", clientSecret). 1274 WithFormField("refresh_token", refreshToken). 1275 WithHost(domain). 1276 Expect().Status(200). 1277 JSON().Object() 1278 1279 obj.ValueEqual("token_type", "bearer") 1280 obj.ValueEqual("scope", "files:read") 1281 obj.NotContainsKey("refresh_token") 1282 1283 assertValidToken(t, testInstance, obj.Value("access_token").String().Raw(), "access", clientID, "files:read") 1284 }) 1285 1286 t.Run("OAuthWithPKCE", func(t *testing.T) { 1287 e := testutils.CreateTestClient(t, ts.URL) 1288 1289 /* Values taken from https://datatracker.ietf.org/doc/html/rfc7636#appendix-B */ 1290 challenge := "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM" 1291 verifier := "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" 1292 1293 /* 1. GET /auth/authorize */ 1294 resBody := e.GET("/auth/authorize"). 1295 WithQuery("response_type", "code"). 1296 WithQuery("state", "123456"). 1297 WithQuery("redirect_uri", "https://example.org/oauth/callback"). 1298 WithQuery("scope", "files:read"). 1299 WithQuery("client_id", clientID). 1300 WithQuery("code_challenge", challenge). 1301 WithQuery("code_challenge_method", "S256"). 1302 WithCookie(session.CookieName(testInstance), sessionID). 1303 WithHost(domain). 1304 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1305 Expect().Status(200). 1306 ContentType("text/html", "utf-8"). 1307 Body() 1308 1309 matches := resBody.Match(`<input type="hidden" name="csrf_token" value="(\w+)"`) 1310 matches.Length().Equal(2) 1311 csrfToken = matches.Index(1).Raw() 1312 1313 /* 2. POST /auth/authorize */ 1314 e.POST("/auth/authorize"). 1315 WithFormField("state", "123456"). 1316 WithFormField("client_id", clientID). 1317 WithFormField("scope", "files:read"). 1318 WithFormField("redirect_uri", "https://example.org/oauth/callback"). 1319 WithFormField("csrf_token", csrfToken). 1320 WithFormField("response_type", "code"). 1321 WithFormField("code_challenge", challenge). 1322 WithFormField("code_challenge_method", "S256"). 1323 WithHost(domain). 1324 WithCookie("_csrf", csrfToken). 1325 WithCookie(session.CookieName(testInstance), sessionID). 1326 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1327 Expect().Status(302) 1328 1329 var results []oauth.AccessCode 1330 allReq := &couchdb.AllDocsRequest{} 1331 err := couchdb.GetAllDocs(testInstance, consts.OAuthAccessCodes, allReq, &results) 1332 assert.NoError(t, err) 1333 var code string 1334 for _, result := range results { 1335 if result.Challenge != "" { 1336 code = result.Code 1337 } 1338 } 1339 require.NotEmpty(t, code) 1340 1341 /* 3. POST /auth/access_token without code_verifier must fail */ 1342 e.POST("/auth/access_token"). 1343 WithFormField("grant_type", "authorization_code"). 1344 WithFormField("client_id", clientID). 1345 WithFormField("client_secret", clientSecret). 1346 WithFormField("code", code). 1347 WithHost(domain). 1348 Expect().Status(400). 1349 JSON().Object(). 1350 ValueEqual("error", "invalid code_verifier") 1351 1352 /* 4. POST /auth/access_token with code_verifier should succeed */ 1353 obj := e.POST("/auth/access_token"). 1354 WithFormField("grant_type", "authorization_code"). 1355 WithFormField("client_id", clientID). 1356 WithFormField("client_secret", clientSecret). 1357 WithFormField("code", code). 1358 WithFormField("code_verifier", verifier). 1359 WithHost(domain). 1360 Expect().Status(200). 1361 JSON().Object() 1362 1363 obj.ValueEqual("token_type", "bearer") 1364 obj.ValueEqual("scope", "files:read") 1365 1366 assertValidToken(t, testInstance, obj.Value("access_token").String().Raw(), "access", clientID, "files:read") 1367 assertValidToken(t, testInstance, obj.Value("refresh_token").String().Raw(), "refresh", clientID, "files:read") 1368 }) 1369 1370 t.Run("ConfirmFlagship", func(t *testing.T) { 1371 e := testutils.CreateTestClient(t, ts.URL) 1372 1373 token, code, err := oauth.GenerateConfirmCode(testInstance, clientID) 1374 require.NoError(t, err) 1375 1376 e.POST("/auth/clients/"+clientID+"/flagship"). 1377 WithFormField("code", code). 1378 WithFormField("token", string(token)). 1379 WithHost(domain). 1380 Expect().Status(204) 1381 1382 client, err := oauth.FindClient(testInstance, clientID) 1383 require.NoError(t, err) 1384 assert.True(t, client.Flagship) 1385 }) 1386 1387 t.Run("LoginFlagship", func(t *testing.T) { 1388 oauthClient := &oauth.Client{ 1389 RedirectURIs: []string{"cozy://flagship"}, 1390 ClientName: "Cozy Flagship", 1391 ClientKind: "mobile", 1392 SoftwareID: "cozy-flagship", 1393 SoftwareVersion: "0.1.0", 1394 } 1395 1396 require.Nil(t, oauthClient.Create(testInstance)) 1397 client, err := oauth.FindClient(testInstance, oauthClient.ClientID) 1398 require.NoError(t, err) 1399 client.CertifiedFromStore = true 1400 require.NoError(t, client.SetFlagship(testInstance)) 1401 1402 t.Run("WithAnInvalidPassPhrase", func(t *testing.T) { 1403 e := testutils.CreateTestClient(t, ts.URL) 1404 1405 e.POST("/auth/login/flagship"). 1406 WithHeader("Accept", "application/json"). 1407 WithJSON(map[string]string{ 1408 "passphrase": "InvalidPassphrase", 1409 "client_id": client.CouchID, 1410 "client_secret": client.ClientSecret, 1411 }). 1412 WithHost(domain). 1413 Expect().Status(401) 1414 }) 1415 1416 t.Run("WithAnInvalidClientSecret", func(t *testing.T) { 1417 e := testutils.CreateTestClient(t, ts.URL) 1418 1419 e.POST("/auth/login/flagship"). 1420 WithHeader("Accept", "application/json"). 1421 WithJSON(map[string]string{ 1422 "passphrase": "MyPassphrase", 1423 "client_id": client.CouchID, 1424 "client_secret": "InvalidClientSecret", 1425 }). 1426 WithHost(domain). 1427 Expect().Status(400) 1428 }) 1429 1430 t.Run("Success", func(t *testing.T) { 1431 e := testutils.CreateTestClient(t, ts.URL) 1432 1433 obj := e.POST("/auth/login/flagship"). 1434 WithHeader("Accept", "application/json"). 1435 WithJSON(map[string]string{ 1436 "passphrase": "MyPassphrase", 1437 "client_id": client.CouchID, 1438 "client_secret": client.ClientSecret, 1439 }). 1440 WithHost(domain). 1441 Expect().Status(200). 1442 JSON().Object() 1443 1444 obj.Value("access_token").String().NotEmpty() 1445 obj.Value("refresh_token").String().NotEmpty() 1446 obj.ValueEqual("scope", "*") 1447 obj.ValueEqual("token_type", "bearer") 1448 }) 1449 }) 1450 1451 t.Run("LogoutNoToken", func(t *testing.T) { 1452 e := testutils.CreateTestClient(t, ts.URL) 1453 1454 e.DELETE("/auth/login"). 1455 WithHost(domain). 1456 Expect().Status(401) 1457 }) 1458 1459 t.Run("LogoutSuccess", func(t *testing.T) { 1460 e := testutils.CreateTestClient(t, ts.URL) 1461 1462 token := testInstance.BuildAppToken("home", "") 1463 1464 _, err := permission.CreateWebappSet(testInstance, "home", permission.Set{}, "1.0.0") 1465 assert.NoError(t, err) 1466 1467 e.DELETE("/auth/login"). 1468 WithHeader("Authorization", "Bearer "+token). 1469 WithHost(domain). 1470 Expect().Status(204) 1471 1472 err = permission.DestroyWebapp(testInstance, "home") 1473 require.NoError(t, err) 1474 }) 1475 1476 t.Run("LogoutOthers", func(t *testing.T) { 1477 // First two connexion 1478 e1 := testutils.CreateTestClient(t, ts.URL) 1479 e2 := testutils.CreateTestClient(t, ts.URL) 1480 1481 // Third connexion closing e2 using the cookies from e1 1482 e3 := testutils.CreateTestClient(t, ts.URL) 1483 1484 // Authenticate user 1 1485 csrfToken := getLoginCSRFToken(e1) 1486 res := e1.POST("/auth/login"). 1487 WithFormField("passphrase", "MyPassphrase"). 1488 WithFormField("csrf_token", csrfToken). 1489 WithCookie("_csrf", csrfToken). 1490 WithHost(domain). 1491 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1492 Expect().Status(303) 1493 1494 res.Cookie("_csrf").Value().NotEmpty() 1495 1496 // Retrieve the session id from cozysessid 1497 rawSessID1 := res.Cookie("cozysessid").Value().NotEmpty().Raw() 1498 b, err := base64.RawURLEncoding.DecodeString(rawSessID1) 1499 require.NoError(t, err) 1500 sessionID1 := string(b[8 : 8+32]) 1501 1502 // Authenticate user 2 1503 csrfToken = getLoginCSRFToken(e2) 1504 res = e2.POST("/auth/login"). 1505 WithFormField("passphrase", "MyPassphrase"). 1506 WithFormField("csrf_token", csrfToken). 1507 WithCookie("_csrf", csrfToken). 1508 WithHost(domain). 1509 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1510 Expect().Status(303) 1511 1512 res.Cookie("_csrf").Value().NotEmpty() 1513 res.Cookie("cozysessid").Value().NotEmpty() 1514 1515 token := testInstance.BuildAppToken("home", sessionID1) 1516 _, err = permission.CreateWebappSet(testInstance, "home", permission.Set{}, "1.0.0") 1517 assert.NoError(t, err) 1518 1519 // Delete all the other sessions 1520 e3.DELETE("/auth/login/others"). 1521 WithHost(domain). 1522 WithHeader("Authorization", "Bearer "+token). 1523 WithCookie("cozysessid", rawSessID1). 1524 Expect().Status(204) 1525 1526 // Delete all the other sessions again give the same output 1527 e3.DELETE("/auth/login/others"). 1528 WithHost(domain). 1529 WithHeader("Authorization", "Bearer "+token). 1530 WithCookie("cozysessid", rawSessID1). 1531 Expect().Status(204) 1532 1533 err = permission.DestroyWebapp(testInstance, "home") 1534 assert.NoError(t, err) 1535 }) 1536 1537 t.Run("PassphraseResetLoggedIn", func(t *testing.T) { 1538 e := testutils.CreateTestClient(t, ts.URL) 1539 1540 body := e.GET("/auth/passphrase_reset"). 1541 WithHost(domain). 1542 Expect().Status(200). 1543 ContentType("text/html", "utf-8"). 1544 Body() 1545 1546 body.Contains("my password") 1547 body.Contains(`<input type="hidden" name="csrf_token"`) 1548 }) 1549 1550 t.Run("PassphraseReset", func(t *testing.T) { 1551 e := testutils.CreateTestClient(t, ts.URL) 1552 1553 csrfToken := e.GET("/auth/passphrase_reset"). 1554 WithHost(domain). 1555 Expect().Status(200). 1556 Cookie("_csrf").Value().Raw() 1557 1558 e.POST("/auth/passphrase_reset"). 1559 WithFormField("csrf_token", csrfToken). 1560 WithCookie("_csrf", csrfToken). 1561 WithHost(domain). 1562 Expect().Status(200). 1563 ContentType("text/html", "utf-8") 1564 }) 1565 1566 t.Run("PassphraseRenewFormNoToken", func(t *testing.T) { 1567 e := testutils.CreateTestClient(t, ts.URL) 1568 1569 e.GET("/auth/passphrase_renew"). 1570 WithHost(domain). 1571 Expect().Status(400). 1572 ContentType("text/html", "utf-8"). 1573 Body().Contains(`The link to reset the password is truncated or has expired`) 1574 }) 1575 1576 t.Run("PassphraseRenewFormBadToken", func(t *testing.T) { 1577 e := testutils.CreateTestClient(t, ts.URL) 1578 1579 e.GET("/auth/passphrase_renew"). 1580 WithQuery("token", "invalid"). // invalid 1581 WithHost(domain). 1582 Expect().Status(400). 1583 ContentType("text/html", "utf-8"). 1584 Body().Contains(`The link to reset the password is truncated or has expired`) 1585 }) 1586 1587 t.Run("PassphraseRenewFormWithToken", func(t *testing.T) { 1588 e := testutils.CreateTestClient(t, ts.URL) 1589 1590 e.GET("/auth/passphrase_renew"). 1591 WithQuery("token", "badbee"). // good format but invalid 1592 WithHost(domain). 1593 Expect().Status(400). 1594 JSON().Object(). 1595 ValueEqual("error", "invalid_token") 1596 }) 1597 1598 t.Run("PassphraseRenew", func(t *testing.T) { 1599 e := testutils.CreateTestClient(t, ts.URL) 1600 1601 d := "test.cozycloud.cc.web_reset_form" 1602 _ = lifecycle.Destroy(d) 1603 in1, err := lifecycle.Create(&lifecycle.Options{ 1604 Domain: d, 1605 Locale: "en", 1606 Email: "alice@example.com", 1607 }) 1608 require.NoError(t, err) 1609 t.Cleanup(func() { _ = lifecycle.Destroy(d) }) 1610 1611 err = lifecycle.RegisterPassphrase(in1, in1.RegisterToken, lifecycle.PassParameters{ 1612 Pass: []byte("MyPass"), 1613 Iterations: 5000, 1614 Key: "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=", 1615 }) 1616 require.NoError(t, err) 1617 1618 csrfToken := e.GET("/auth/passphrase_reset"). 1619 WithHost(d). 1620 Expect().Status(200). 1621 Cookie("_csrf").Value().Raw() 1622 1623 e.POST("/auth/passphrase_reset"). 1624 WithFormField("csrf_token", csrfToken). 1625 WithCookie("_csrf", csrfToken). 1626 WithHost(d). 1627 Expect().Status(200) 1628 1629 in2, err := instance.Get(d) 1630 require.NoError(t, err) 1631 1632 e.POST("/auth/passphrase_renew"). 1633 WithFormField("passphrase_reset_token", hex.EncodeToString(in2.PassphraseResetToken)). 1634 WithFormField("passphrase", "NewPassphrase"). 1635 WithFormField("csrf_token", csrfToken). 1636 WithCookie("_csrf", csrfToken). 1637 WithHost(d). 1638 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1639 Expect().Status(303). 1640 Header("Location"). 1641 Equal("https://test.cozycloud.cc.web_reset_form/auth/login") 1642 }) 1643 1644 t.Run("IsLoggedOutAfterLogout", func(t *testing.T) { 1645 e := testutils.CreateTestClient(t, ts.URL) 1646 1647 e.GET("/test"). 1648 WithHost(domain). 1649 Expect().Status(200). 1650 Text(httpexpect.ContentOpts{MediaType: "text/plain"}). 1651 Equal("who_are_you") 1652 }) 1653 1654 t.Run("PassphraseOnboarding", func(t *testing.T) { 1655 // e := testutils.CreateTestClient(t, ts.URL) 1656 e := httpexpect.WithConfig(httpexpect.Config{ 1657 TestName: t.Name(), 1658 BaseURL: ts.URL, 1659 Reporter: httpexpect.NewAssertReporter(t), 1660 Printers: []httpexpect.Printer{ 1661 httpexpect.NewDebugPrinter(t, true), 1662 }, 1663 }) 1664 1665 // Here we create a new instance without passphrase 1666 d := "test.cozycloud.cc.web_passphrase" 1667 _ = lifecycle.Destroy(d) 1668 inst, err := lifecycle.Create(&lifecycle.Options{ 1669 Domain: d, 1670 Locale: "en", 1671 Email: "alice@example.com", 1672 }) 1673 require.NoError(t, err) 1674 require.False(t, inst.OnboardingFinished) 1675 1676 // Should redirect to /auth/passphrase 1677 e.GET("/"). 1678 WithQuery("registerToken", hex.EncodeToString(inst.RegisterToken)). 1679 WithHost(inst.Domain). 1680 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1681 Expect().Status(303). 1682 Header("Location").Contains("/auth/passphrase?registerToken=") 1683 1684 // Adding a passphrase and check if we are redirected to home 1685 pass := []byte("passphrase") 1686 err = lifecycle.RegisterPassphrase(inst, inst.RegisterToken, lifecycle.PassParameters{ 1687 Pass: pass, 1688 Iterations: 5000, 1689 Key: "0.uRcMe+Mc2nmOet4yWx9BwA==|PGQhpYUlTUq/vBEDj1KOHVMlTIH1eecMl0j80+Zu0VRVfFa7X/MWKdVM6OM/NfSZicFEwaLWqpyBlOrBXhR+trkX/dPRnfwJD2B93hnLNGQ=", 1690 }) 1691 assert.NoError(t, err) 1692 1693 inst.OnboardingFinished = true 1694 1695 e.GET("/"). 1696 WithQuery("registerToken", hex.EncodeToString(inst.RegisterToken)). 1697 WithHost(domain). 1698 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1699 Expect().Status(303). 1700 Header("Location").Contains("/auth/login") 1701 }) 1702 1703 t.Run("PassphraseOnboardingFinished", func(t *testing.T) { 1704 e := testutils.CreateTestClient(t, ts.URL) 1705 1706 // Using the testInstance which had already been onboarded 1707 // Should redirect to home 1708 e.GET("/auth/passphrase"). 1709 WithHost(domain). 1710 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1711 Expect().Status(303). 1712 Header("Location").Equal("https://home.cozy.example.net/") 1713 }) 1714 1715 t.Run("PassphraseOnboardingBadRegisterToken", func(t *testing.T) { 1716 e := testutils.CreateTestClient(t, ts.URL) 1717 1718 // Should render need_onboarding 1719 d := "test.cozycloud.cc.web_passphrase_bad_token" 1720 _ = lifecycle.Destroy(d) 1721 inst, err := lifecycle.Create(&lifecycle.Options{ 1722 Domain: d, 1723 Locale: "en", 1724 Email: "alice@example.com", 1725 }) 1726 assert.NoError(t, err) 1727 assert.False(t, inst.OnboardingFinished) 1728 1729 // Should redirect to /auth/passphrase 1730 e.GET("/auth/passphrase"). 1731 WithQuery("registerToken", "coincoin"). 1732 WithHost(inst.Domain). 1733 WithRedirectPolicy(httpexpect.DontFollowRedirects). 1734 Expect().Status(200). 1735 ContentType("text/html", "utf-8"). 1736 Body().Contains("Your Cozy has not been yet activated.") 1737 }) 1738 1739 t.Run("LoginOnboardingNotFinished", func(t *testing.T) { 1740 e := testutils.CreateTestClient(t, ts.URL) 1741 1742 // Should render need_onboarding 1743 d := "test.cozycloud.cc.web_login_onboarding_not_finished" 1744 _ = lifecycle.Destroy(d) 1745 inst, err := lifecycle.Create(&lifecycle.Options{ 1746 Domain: d, 1747 Locale: "en", 1748 Email: "alice@example.com", 1749 }) 1750 assert.NoError(t, err) 1751 assert.False(t, inst.OnboardingFinished) 1752 1753 // Should redirect to /auth/passphrase 1754 e.GET("/auth/login"). 1755 WithHost(inst.Domain). 1756 Expect().Status(200). 1757 ContentType("text/html", "utf-8"). 1758 Body().Contains("Your Cozy has not been yet activated.") 1759 }) 1760 1761 t.Run("ShowConfirmForm", func(t *testing.T) { 1762 e := testutils.CreateTestClient(t, ts.URL) 1763 1764 body := e.GET("/auth/confirm"). 1765 WithQuery("state", "342dd650-599b-0139-cfb0-543d7eb8149c"). 1766 WithHost(domain). 1767 Expect().Status(200). 1768 ContentType("text/html", "utf-8"). 1769 Body() 1770 1771 body.Contains(`<input id="state" type="hidden" name="state" value="342dd650-599b-0139-cfb0-543d7eb8149c" />`) 1772 body.NotContains("myfragment") 1773 }) 1774 1775 t.Run("SendConfirmBadCSRFToken", func(t *testing.T) { 1776 e := testutils.CreateTestClient(t, ts.URL) 1777 1778 e.POST("/auth/confirm"). 1779 WithHeader("Accept", "application/json"). 1780 WithFormField("passphrase", "MyPassphrase"). 1781 WithFormField("csrf_token", "123456"). 1782 WithFormField("state", "342dd650-599b-0139-cfb0-543d7eb8149c"). 1783 WithCookie("_csrf", csrfToken). 1784 WithHost(domain). 1785 Expect().Status(403) 1786 }) 1787 1788 t.Run("SendConfirmBadPass", func(t *testing.T) { 1789 e := testutils.CreateTestClient(t, ts.URL) 1790 1791 token := getConfirmCSRFToken(e) 1792 1793 e.POST("/auth/confirm"). 1794 WithHeader("Accept", "application/json"). 1795 WithFormField("passphrase", "InvalidPassphrase"). // invalid 1796 WithFormField("csrf_token", token). 1797 WithFormField("state", "342dd650-599b-0139-cfb0-543d7eb8149c"). 1798 WithCookie("_csrf", token). 1799 WithHost(domain). 1800 Expect().Status(401) 1801 }) 1802 1803 t.Run("SendConfirmOK", func(t *testing.T) { 1804 var confirmCode string 1805 1806 t.Run("GetConfirmCode", func(t *testing.T) { 1807 e := testutils.CreateTestClient(t, ts.URL) 1808 1809 token := getConfirmCSRFToken(e) 1810 1811 obj := e.POST("/auth/confirm"). 1812 WithHeader("Accept", "application/json"). 1813 WithFormField("passphrase", "MyPassphrase"). 1814 WithFormField("csrf_token", token). 1815 WithFormField("state", "342dd650-599b-0139-cfb0-543d7eb8149c"). 1816 WithCookie("_csrf", token). 1817 WithHost(domain). 1818 Expect().Status(200). 1819 JSON().Object() 1820 1821 redirect := obj.Value("redirect").String().Raw() 1822 1823 u, err := url.Parse(redirect) 1824 assert.NoError(t, err) 1825 1826 confirmCode = u.Query().Get("code") 1827 assert.NotEmpty(t, confirmCode) 1828 state := u.Query().Get("state") 1829 assert.Equal(t, "342dd650-599b-0139-cfb0-543d7eb8149c", state) 1830 }) 1831 1832 t.Run("ConfirmBadCode", func(t *testing.T) { 1833 e := testutils.CreateTestClient(t, ts.URL) 1834 1835 e.GET("/auth/confirm/123456"). 1836 WithHost(domain). 1837 Expect().Status(401) 1838 }) 1839 1840 t.Run("ConfirmCodeOK", func(t *testing.T) { 1841 e := testutils.CreateTestClient(t, ts.URL) 1842 1843 e.GET("/auth/confirm/" + confirmCode). 1844 WithHost(domain). 1845 Expect().Status(204) 1846 }) 1847 }) 1848 1849 t.Run("KonnectorTokens", func(t *testing.T) { 1850 konnSlug, err := setup.InstallMiniKonnector() 1851 require.NoError(t, err, "Could not install mini konnector.") 1852 1853 t.Run("BuildKonnectorToken", func(t *testing.T) { 1854 e := testutils.CreateTestClient(t, ts.URL) 1855 1856 // Create an flagship OAuth client 1857 oauthClient := oauth.Client{ 1858 RedirectURIs: []string{"cozy://client"}, 1859 ClientName: "oauth-client", 1860 SoftwareID: "github.com/cozy/cozy-stack/testing/client", 1861 Flagship: true, 1862 } 1863 require.Nil(t, oauthClient.Create(testInstance, oauth.NotPending)) 1864 1865 // Give it the maximal permission 1866 token, err := testInstance.MakeJWT(consts.AccessTokenAudience, 1867 oauthClient.ClientID, "*", "", time.Now()) 1868 require.NoError(t, err) 1869 1870 // Get konnector access_token 1871 konnToken := e.POST("/auth/tokens/konnectors/"+konnSlug). 1872 WithHeader("Accept", "application/json"). 1873 WithHeader("Authorization", "Bearer "+token). 1874 WithHost(domain). 1875 Expect().Status(201). 1876 JSON().String().Raw() 1877 1878 // Validate token 1879 claims := permission.Claims{} 1880 err = crypto.ParseJWT(konnToken, func(token *jwt.Token) (interface{}, error) { 1881 return testInstance.SessionSecret(), nil 1882 }, &claims) 1883 assert.NoError(t, err) 1884 assert.Equal(t, consts.KonnectorAudience, claims.Audience[0]) 1885 assert.Equal(t, domain, claims.Issuer) 1886 assert.Equal(t, konnSlug, claims.Subject) 1887 assert.Equal(t, "", claims.Scope) 1888 }) 1889 1890 t.Run("BuildKonnectorTokenNotFlagshipApp", func(t *testing.T) { 1891 e := testutils.CreateTestClient(t, ts.URL) 1892 1893 // Create an OAuth client 1894 oauthClient := oauth.Client{ 1895 RedirectURIs: []string{"cozy://client"}, 1896 ClientName: "oauth-client", 1897 SoftwareID: "github.com/cozy/cozy-stack/testing/client", 1898 Flagship: false, 1899 } 1900 require.Nil(t, oauthClient.Create(testInstance, oauth.NotPending)) 1901 1902 // Give it the maximal permission 1903 token, err := testInstance.MakeJWT(consts.AccessTokenAudience, 1904 oauthClient.ClientID, "*", "", time.Now()) 1905 require.NoError(t, err) 1906 1907 e.POST("/auth/tokens/konnectors/"+konnSlug). 1908 WithHeader("Accept", "application/json"). 1909 WithHeader("Authorization", "Bearer "+token). 1910 WithHost(domain). 1911 Expect().Status(403) 1912 }) 1913 1914 t.Run("BuildKonnectorTokenInvalidSlug", func(t *testing.T) { 1915 e := testutils.CreateTestClient(t, ts.URL) 1916 1917 // Create an flagship OAuth client 1918 oauthClient := oauth.Client{ 1919 RedirectURIs: []string{"cozy://client"}, 1920 ClientName: "oauth-client", 1921 SoftwareID: "github.com/cozy/cozy-stack/testing/client", 1922 Flagship: true, 1923 } 1924 require.Nil(t, oauthClient.Create(testInstance, oauth.NotPending)) 1925 1926 // Give it the maximal permission 1927 token, err := testInstance.MakeJWT(consts.AccessTokenAudience, 1928 oauthClient.ClientID, "*", "", time.Now()) 1929 require.NoError(t, err) 1930 1931 e.POST("/auth/tokens/konnectors/missin"). 1932 WithHeader("Accept", "application/json"). 1933 WithHeader("Authorization", "Bearer "+token). 1934 WithHost(domain). 1935 Expect().Status(404) 1936 }) 1937 }) 1938 1939 t.Run("Share by link protected by password", func(t *testing.T) { 1940 e := testutils.CreateTestClient(t, ts.URL) 1941 1942 set := permission.Set{ 1943 permission.Rule{Type: consts.Files, Title: "files"}, 1944 } 1945 parent := &permission.Permission{ 1946 Type: permission.TypeWebapp, 1947 Permissions: set, 1948 } 1949 sourceID := "io.cozy.apps/drive" 1950 codes := map[string]string{"email": "123456789123456789"} 1951 shortcodes := map[string]string{"email": "123456"} 1952 md, err := metadata.NewWithApp("drive", "", permission.DocTypeVersion) 1953 require.NoError(t, err) 1954 subdoc := permission.Permission{ 1955 Permissions: set, 1956 Password: "the_password!", 1957 Metadata: md, 1958 } 1959 perm, err := permission.CreateShareSet(testInstance, parent, sourceID, codes, shortcodes, subdoc, nil) 1960 require.NoError(t, err) 1961 1962 e.POST("/auth/share-by-link/password"). 1963 WithHeader("Accept", "application/json"). 1964 WithFormField("perm_id", perm.ID()). 1965 WithFormField("password", "bad_password"). 1966 WithHost(domain). 1967 Expect().Status(403) 1968 1969 res := e.POST("/auth/share-by-link/password"). 1970 WithHeader("Accept", "application/json"). 1971 WithFormField("perm_id", perm.ID()). 1972 WithFormField("password", "the_password!"). 1973 WithHost(domain). 1974 Expect().Status(200) 1975 1976 res.Cookies().Length().Equal(1) 1977 res.Cookie("pass" + perm.ID()).Value().NotEmpty() 1978 }) 1979 1980 t.Run("MagicLink", func(t *testing.T) { 1981 e := testutils.CreateTestClient(t, ts.URL) 1982 1983 d := "test.cozycloud.cc.magic_link" 1984 _ = lifecycle.Destroy(d) 1985 magicLink := true 1986 inst, err := lifecycle.Create(&lifecycle.Options{ 1987 Domain: d, 1988 Locale: "en", 1989 Email: "alice@example.com", 1990 MagicLink: &magicLink, 1991 }) 1992 require.NoError(t, err) 1993 t.Cleanup(func() { _ = lifecycle.Destroy(d) }) 1994 1995 t.Run("Failure", func(t *testing.T) { 1996 code := "badcode" 1997 1998 e.GET("/auth/magic_link"). 1999 WithHost(d). 2000 WithRedirectPolicy(httpexpect.DontFollowRedirects). 2001 WithQuery("code", code). 2002 Expect().Status(400) 2003 }) 2004 2005 t.Run("Success", func(t *testing.T) { 2006 code, err := lifecycle.CreateMagicLinkCode(inst) 2007 require.NoError(t, err) 2008 2009 e.GET("/auth/magic_link"). 2010 WithHost(d). 2011 WithRedirectPolicy(httpexpect.DontFollowRedirects). 2012 WithQuery("code", code). 2013 Expect().Status(303). 2014 Header("Location").Equal("https://home." + d + "/") 2015 }) 2016 2017 t.Run("Flagship", func(t *testing.T) { 2018 oauthClient := &oauth.Client{ 2019 RedirectURIs: []string{"cozy://flagship"}, 2020 ClientName: "Cozy Flagship", 2021 ClientKind: "mobile", 2022 SoftwareID: "cozy-flagship", 2023 SoftwareVersion: "0.1.0", 2024 } 2025 2026 require.Nil(t, oauthClient.Create(inst)) 2027 client, err := oauth.FindClient(inst, oauthClient.ClientID) 2028 require.NoError(t, err) 2029 client.CertifiedFromStore = true 2030 require.NoError(t, client.SetFlagship(inst)) 2031 2032 code, err := lifecycle.CreateMagicLinkCode(inst) 2033 require.NoError(t, err) 2034 2035 obj := e.POST("/auth/magic_link/flagship"). 2036 WithHost(d). 2037 WithHeader("Accept", "application/json"). 2038 WithJSON(map[string]string{ 2039 "magic_code": code, 2040 "client_id": client.CouchID, 2041 "client_secret": client.ClientSecret, 2042 }). 2043 Expect().Status(200). 2044 JSON().Object() 2045 2046 obj.Value("access_token").String().NotEmpty() 2047 obj.Value("refresh_token").String().NotEmpty() 2048 obj.ValueEqual("scope", "*") 2049 obj.ValueEqual("token_type", "bearer") 2050 }) 2051 }) 2052 } 2053 2054 func getLoginCSRFToken(e *httpexpect.Expect) string { 2055 return e.GET("/auth/login"). 2056 WithHost(domain). 2057 Expect().Status(200). 2058 Cookie("_csrf").Value().Raw() 2059 } 2060 2061 func getConfirmCSRFToken(e *httpexpect.Expect) string { 2062 return e.GET("/auth/confirm"). 2063 WithQuery("state", "123"). 2064 WithHost(domain). 2065 Expect().Status(200). 2066 Cookie("_csrf").Value().Raw() 2067 } 2068 2069 func fakeAPI(g *echo.Group) { 2070 g.Use(middlewares.NeedInstance, middlewares.LoadSession) 2071 g.GET("", func(c echo.Context) error { 2072 var content string 2073 if middlewares.IsLoggedIn(c) { 2074 content = "logged_in" 2075 } else { 2076 content = "who_are_you" 2077 } 2078 return c.String(http.StatusOK, content) 2079 }) 2080 } 2081 2082 func assertValidToken(t *testing.T, testInstance *instance.Instance, token, audience, subject, scope string) { 2083 claims := permission.Claims{} 2084 err := crypto.ParseJWT(token, func(token *jwt.Token) (interface{}, error) { 2085 return testInstance.OAuthSecret, nil 2086 }, &claims) 2087 assert.NoError(t, err) 2088 assert.Equal(t, audience, claims.Audience[0]) 2089 assert.Equal(t, domain, claims.Issuer) 2090 assert.Equal(t, subject, claims.Subject) 2091 assert.Equal(t, scope, claims.Scope) 2092 }