github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/apps/apps_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 apps_test 5 6 import ( 7 "archive/tar" 8 "bytes" 9 "compress/gzip" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "io" 14 "net/http" 15 "net/url" 16 "os" 17 "path/filepath" 18 "testing" 19 "time" 20 21 apps "github.com/cozy/cozy-stack/model/app" 22 "github.com/cozy/cozy-stack/model/feature" 23 "github.com/cozy/cozy-stack/model/instance" 24 "github.com/cozy/cozy-stack/model/instance/lifecycle" 25 "github.com/cozy/cozy-stack/model/intent" 26 "github.com/cozy/cozy-stack/model/oauth" 27 "github.com/cozy/cozy-stack/model/session" 28 "github.com/cozy/cozy-stack/model/stack" 29 "github.com/cozy/cozy-stack/pkg/assets" 30 "github.com/cozy/cozy-stack/pkg/assets/dynamic" 31 "github.com/cozy/cozy-stack/pkg/assets/model" 32 "github.com/cozy/cozy-stack/pkg/config/config" 33 "github.com/cozy/cozy-stack/pkg/consts" 34 "github.com/cozy/cozy-stack/pkg/filetype" 35 "github.com/cozy/cozy-stack/tests/testutils" 36 "github.com/cozy/cozy-stack/web" 37 webApps "github.com/cozy/cozy-stack/web/apps" 38 "github.com/cozy/cozy-stack/web/auth" 39 "github.com/gavv/httpexpect/v2" 40 "github.com/labstack/echo/v4" 41 "github.com/sirupsen/logrus" 42 43 "github.com/stretchr/testify/assert" 44 "github.com/stretchr/testify/require" 45 ) 46 47 const domain = "cozywithapps.example.net" 48 49 func TestApps(t *testing.T) { 50 if testing.Short() { 51 t.Skip("an instance is required for this test: test skipped due to the use of --short flag") 52 } 53 54 config.UseTestFile(t) 55 config.GetConfig().Assets = "../../assets" 56 testutils.NeedCouchdb(t) 57 setup := testutils.NewSetup(t, t.Name()) 58 setup.SetupSwiftTest() 59 60 require.NoError(t, dynamic.InitDynamicAssetFS(config.FsURL().String()), "Could not init dynamic FS") 61 tempdir := t.TempDir() 62 63 cfg := config.GetConfig() 64 cfg.Fs.URL = &url.URL{ 65 Scheme: "file", 66 Host: "localhost", 67 Path: tempdir, 68 } 69 cfg.Contexts[config.DefaultInstanceContext] = map[string]interface{}{"manager_url": "http://manager.example.org"} 70 was := cfg.Subdomains 71 cfg.Subdomains = config.NestedSubdomains 72 defer func() { cfg.Subdomains = was }() 73 74 pass := "aephe2Ei" 75 testInstance := setup.GetTestInstance(&lifecycle.Options{Domain: domain}) 76 params := lifecycle.PassParameters{ 77 Key: "fake-encrypt-key", 78 Iterations: 0, 79 } 80 _ = lifecycle.ForceUpdatePassphrase(testInstance, []byte(pass), params) 81 testInstance.RegisterToken = nil 82 testInstance.OnboardingFinished = true 83 _ = instance.Update(testInstance) 84 85 slug, err := setup.InstallMiniApp() 86 require.NoError(t, err, "Could not install mini app") 87 88 konnectorSlug, err := setup.InstallMiniKonnector() 89 require.NoError(t, err, "Could not install mini konnector") 90 91 clientSideSlug, err := setup.InstallMiniClientSideKonnector() 92 require.NoError(t, err, "Could not install miniClientSideKonnector konnector") 93 94 ts := setup.GetTestServer("/apps", webApps.WebappsRoutes, func(r *echo.Echo) *echo.Echo { 95 r.POST("/login", func(c echo.Context) error { 96 sess, _ := session.New(testInstance, session.LongRun) 97 cookie, _ := sess.ToCookie() 98 c.SetCookie(cookie) 99 return c.HTML(http.StatusOK, "OK") 100 }) 101 r.POST("/auth/session_code", auth.CreateSessionCode) 102 router, err := web.CreateSubdomainProxy(r, &stack.Services{}, webApps.Serve) 103 require.NoError(t, err, "Cant start subdoman proxy") 104 return router 105 }) 106 t.Cleanup(ts.Close) 107 108 // Login 109 cozysessID := testutils.CreateTestClient(t, ts.URL).POST("/login"). 110 WithHost(testInstance.Domain). 111 Expect().Status(200). 112 Cookie("cozysessid").Value().Raw() 113 114 _, token := setup.GetTestClient(consts.Apps + " " + consts.Konnectors) 115 116 t.Run("Serve", func(t *testing.T) { 117 e := testutils.CreateTestClient(t, ts.URL) 118 119 assertNotPublic(e, slug, testInstance.Domain, "/foo", 302, "https://cozywithapps.example.net/auth/login?redirect=https%3A%2F%2Fmini.cozywithapps.example.net%2Ffoo") 120 assertNotPublic(e, slug, testInstance.Domain, "/foo/hello.tml", 401, "") 121 122 e = e.Builder(func(r *httpexpect.Request) { 123 r.WithCookie("cozysessid", cozysessID) 124 }) 125 126 assertAuthGet(e, slug, testInstance.Domain, "/foo/", "text/html", "utf-8", `this is index.html. <a lang="en" href="https://cozywithapps.example.net/status/">Status</a>`) 127 assertAuthGet(e, slug, testInstance.Domain, "/foo/hello.html", "text/html", "utf-8", "world {{.Token}}") 128 assertAuthGet(e, slug, testInstance.Domain, "/public", "text/html", "utf-8", "this is a file in public/") 129 assertAuthGet(e, slug, testInstance.Domain, "/public/index.html", "text/html", "utf-8", "this is a file in public/") 130 assertAnonGet(e, slug, testInstance.Domain, "/public", "text/html", "utf-8", "this is a file in public/") 131 assertAnonGet(e, slug, testInstance.Domain, "/public/index.html", "text/html", "utf-8", "this is a file in public/") 132 assertNotFound(e, slug, testInstance.Domain, "/404") 133 assertNotFound(e, slug, testInstance.Domain, "/") 134 assertNotFound(e, slug, testInstance.Domain, "/index.html") 135 assertNotFound(e, slug, testInstance.Domain, "/public/hello.html") 136 assertInternalServerError(e, slug, testInstance.Domain, "/invalid") 137 }) 138 139 t.Run("ServeWithClientsLimitExceeded", func(t *testing.T) { 140 e := testutils.CreateTestClient(t, ts.URL) 141 142 // Create the OAuth client for the flagship app 143 flagship := oauth.Client{ 144 RedirectURIs: []string{"cozy://flagship"}, 145 ClientName: "flagship-app", 146 ClientKind: "mobile", 147 SoftwareID: "github.com/cozy/cozy-stack/testing/flagship", 148 Flagship: true, 149 } 150 require.Nil(t, flagship.Create(testInstance, oauth.NotPending)) 151 152 testutils.WithFlag(t, testInstance, "cozy.oauthclients.max", float64(0)) 153 154 e = e.Builder(func(r *httpexpect.Request) { 155 r.WithCookie("cozysessid", cozysessID) 156 }) 157 158 assertAuthGet(e, slug, testInstance.Domain, "/public", "text/html", "utf-8", "this is a file in public/") 159 assertAnonGet(e, slug, testInstance.Domain, "/public", "text/html", "utf-8", "this is a file in public/") 160 161 redirect := testInstance.SubDomain(slug) 162 redirect.Path = "/foo" 163 location := testInstance.PageURL("/settings/clients/limit-exceeded", url.Values{"redirect": {redirect.String()}}) 164 assertRedirect(e, slug, testInstance.Domain, "/foo", 303, location) 165 166 assertAuthGet(e, slug, testInstance.Domain, "/foo/hello.html", "text/html", "utf-8", "world {{.Token}}") 167 168 testInstance.FeatureFlags = map[string]interface{}{} 169 require.NoError(t, instance.Update(testInstance)) 170 }) 171 172 t.Run("CozyBar", func(t *testing.T) { 173 e := testutils.CreateTestClient(t, ts.URL) 174 175 e.GET("/bar/"). 176 WithHost(slug+"."+testInstance.Domain). 177 WithCookie("cozysessid", cozysessID). 178 WithRedirectPolicy(httpexpect.DontFollowRedirects). 179 Expect().Status(200). 180 HasContentType("text/html", "utf-8"). 181 Body(). 182 Contains(`<link rel="stylesheet" type="text/css" href="//cozywithapps.example.net/assets/css/cozy-bar`). 183 Contains(`<script src="//cozywithapps.example.net/assets/js/cozy-bar`) 184 }) 185 186 t.Run("Warnings", func(t *testing.T) { 187 e := testutils.CreateTestClient(t, ts.URL) 188 189 // Moved instance warning 190 191 testInstance.Moved = true 192 require.NoError(t, instance.Update(testInstance)) 193 194 e.GET("/foo/"). 195 WithHost(slug+"."+testInstance.Domain). 196 WithCookie("cozysessid", cozysessID). 197 WithRedirectPolicy(httpexpect.DontFollowRedirects). 198 Expect().Status(200). 199 HasContentType("text/html", "utf-8"). 200 Body(). 201 Contains(`<meta name="user-action-required" data-title="Cozy has been moved" data-code="moved" data-detail="The Cozy has been moved to a new address"`) 202 203 testInstance.Moved = false 204 require.NoError(t, instance.Update(testInstance)) 205 206 // TOS not signed warning 207 208 testutils.WithManager(t, testInstance) 209 210 tosSigned := testInstance.TOSSigned 211 tosLatest := testInstance.TOSLatest 212 tomorrow := time.Now().Add(24 * time.Hour) 213 testInstance.TOSSigned = "1.0.0-20170901" 214 testInstance.TOSLatest = "2.0.0-" + tomorrow.Format("20060102") 215 require.NoError(t, instance.Update(testInstance)) 216 217 notSigned, deadline := testInstance.CheckTOSNotSignedAndDeadline() 218 require.True(t, notSigned) 219 require.Equal(t, deadline, instance.TOSWarning) 220 221 tosLink, err := testInstance.ManagerURL(instance.ManagerTOSURL) 222 require.NoError(t, err) 223 require.NotEmpty(t, tosLink) 224 225 e.GET("/foo/"). 226 WithHost(slug+"."+testInstance.Domain). 227 WithCookie("cozysessid", cozysessID). 228 WithRedirectPolicy(httpexpect.DontFollowRedirects). 229 Expect().Status(200). 230 HasContentType("text/html", "utf-8"). 231 Body(). 232 Contains(`<meta name="user-action-required" data-title="TOS Updated" data-code="tos-updated" data-detail="Terms of services have been updated" data-links="` + tosLink + `"`) 233 234 testInstance.TOSSigned = tosSigned 235 testInstance.TOSLatest = tosLatest 236 require.NoError(t, instance.Update(testInstance)) 237 }) 238 239 t.Run("ServeWithAnIntents", func(t *testing.T) { 240 e := testutils.CreateTestClient(t, ts.URL) 241 242 intent := &intent.Intent{ 243 Action: "PICK", 244 Type: "io.cozy.foos", 245 Client: "io.cozy.apps/test-app", 246 } 247 err := intent.Save(testInstance) 248 require.NoError(t, err) 249 err = intent.FillServices(testInstance) 250 require.NoError(t, err) 251 require.Len(t, intent.Services, 1) 252 err = intent.Save(testInstance) 253 require.NoError(t, err) 254 255 u, err := url.Parse(intent.Services[0].Href) 256 require.NoError(t, err) 257 258 e.GET(u.Path). 259 WithHost(slug+"."+testInstance.Domain). 260 WithQueryString(u.RawQuery). 261 WithCookie("cozysessid", cozysessID). 262 Expect().Status(200). 263 Header(echo.HeaderContentSecurityPolicy). 264 Contains("frame-ancestors 'self' https://test-app.cozywithapps.example.net/;") 265 }) 266 267 t.Run("FaviconWithContext", func(t *testing.T) { 268 e := testutils.CreateTestClient(t, ts.URL) 269 270 context := "foo" 271 272 asset, ok := assets.Get("/favicon.ico", context) 273 if ok { 274 _ = assets.Remove(asset.Name, asset.Context) 275 } 276 // Create and insert an asset in foo context 277 tmpdir := t.TempDir() 278 _, err := os.OpenFile(filepath.Join(tmpdir, "custom_favicon.png"), os.O_RDWR|os.O_CREATE, 0600) 279 assert.NoError(t, err) 280 281 assetsOptions := []model.AssetOption{{ 282 URL: fmt.Sprintf("file://%s", filepath.Join(tmpdir, "custom_favicon.png")), 283 Name: "/favicon.ico", 284 Context: context, 285 }} 286 err = dynamic.RegisterCustomExternals(assetsOptions, 1) 287 assert.NoError(t, err) 288 289 // Test the theme 290 assert.NoError(t, lifecycle.Patch(testInstance, &lifecycle.Options{ 291 ContextName: context, 292 })) 293 assert.NoError(t, err) 294 295 e.GET("/foo"). 296 WithHost(slug+"."+testInstance.Domain). 297 WithCookie("cozysessid", cozysessID). 298 WithRedirectPolicy(httpexpect.DontFollowRedirects). 299 Expect().Status(200). 300 Body(). 301 Contains(`this is index.html. <a lang="en" href="https://cozywithapps.example.net/status/">Status</a>`). 302 Contains(fmt.Sprintf("/assets/ext/%s/favicon.ico", context)). 303 NotContains("/assets/favicon.ico") 304 }) 305 306 t.Run("SessionCode", func(t *testing.T) { 307 e := testutils.CreateTestClient(t, ts.URL) 308 309 // Create the OAuth client for the flagship app 310 flagship := oauth.Client{ 311 RedirectURIs: []string{"cozy://flagship"}, 312 ClientName: "flagship-app", 313 SoftwareID: "github.com/cozy/cozy-stack/testing/flagship", 314 Flagship: true, 315 } 316 assert.Nil(t, flagship.Create(testInstance, oauth.NotPending)) 317 318 // Create a maximal permission for it 319 token, err := testInstance.MakeJWT(consts.AccessTokenAudience, 320 flagship.ClientID, "*", "", time.Now()) 321 assert.NoError(t, err) 322 323 // Create the session code 324 code := e.POST("/auth/session_code"). 325 WithHost(testInstance.Domain). 326 WithHeader("Authorization", "Bearer "+token). 327 Expect().Status(201). 328 JSON().Object(). 329 Value("session_code").String().NotEmpty().Raw() 330 331 // Load a non-public page 332 e.GET("/foo/"). 333 WithQuery("session_code", code). 334 WithHost(slug+"."+testInstance.Domain). 335 WithCookie("cozysessid", cozysessID). 336 Expect().Status(200). 337 Body().Contains("this is index.html") 338 339 // Try again and check that the session code cannot be reused 340 e.GET("/foo/"). 341 WithQuery("session_code", code). 342 WithHost(slug + "." + testInstance.Domain). 343 WithRedirectPolicy(httpexpect.DontFollowRedirects). 344 Expect().Status(302). 345 Header("Location").Contains("/auth/login") 346 }) 347 348 t.Run("ServeAppsWithJWTNotLogged", func(t *testing.T) { 349 e := testutils.CreateTestClient(t, ts.URL) 350 351 config.GetConfig().Subdomains = config.FlatSubdomains 352 appHost := "cozywithapps-mini.example.net" 353 354 rawURL := e.GET("/foo"). 355 WithQuery("jwt", "abc"). 356 WithHost(appHost). 357 WithRedirectPolicy(httpexpect.DontFollowRedirects). 358 WithCookie("cozysessid", cozysessID). 359 Expect().Status(302). 360 Header("Location").Raw() 361 362 location, err := url.Parse(rawURL) 363 require.NoError(t, err) 364 365 assert.Equal(t, "/auth/login", location.Path) 366 assert.Equal(t, testInstance.Domain, location.Host) 367 assert.NotEmpty(t, location.Query().Get("redirect")) 368 assert.Equal(t, "abc", location.Query().Get("jwt")) 369 }) 370 371 t.Run("OauthAppCantInstallApp", func(t *testing.T) { 372 e := testutils.CreateTestClient(t, ts.URL) 373 374 e.POST("/apps/mini-bis"). 375 WithHost(testInstance.Domain). 376 WithQuery("Source", "git://github.com/nono/cozy-mini.git"). 377 WithHeader("Authorization", "Bearer "+token). 378 WithCookie("cozysessid", cozysessID). 379 Expect().Status(403) 380 }) 381 382 t.Run("OauthAppCantUpdateApp", func(t *testing.T) { 383 e := testutils.CreateTestClient(t, ts.URL) 384 385 e.PUT("/apps/mini"). 386 WithHost(testInstance.Domain). 387 WithHeader("Authorization", "Bearer "+token). 388 WithCookie("cozysessid", cozysessID). 389 Expect().Status(403) 390 }) 391 392 t.Run("ListApps", func(t *testing.T) { 393 e := testutils.CreateTestClient(t, ts.URL) 394 395 obj := e.GET("/apps/"). 396 WithHost(testInstance.Domain). 397 WithHeader("Authorization", "Bearer "+token). 398 WithCookie("cozysessid", cozysessID). 399 Expect().Status(200). 400 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 401 Object() 402 403 data := obj.Value("data").Array() 404 data.Length().IsEqual(1) 405 406 elem := data.Value(0).Object() 407 elem.Value("id").String().NotEmpty() 408 elem.HasValue("type", "io.cozy.apps") 409 410 attrs := elem.Value("attributes").Object() 411 attrs.HasValue("name", "Mini") 412 attrs.HasValue("slug", "mini") 413 414 links := elem.Value("links").Object() 415 links.HasValue("self", "/apps/mini") 416 links.HasValue("related", "https://cozywithapps-mini.example.net/") 417 links.HasValue("icon", "/apps/mini/icon/1.0.0") 418 }) 419 420 t.Run("IconForApp", func(t *testing.T) { 421 e := testutils.CreateTestClient(t, ts.URL) 422 423 e.GET("/apps/mini/icon"). 424 WithHost(testInstance.Domain). 425 WithHeader("Authorization", "Bearer "+token). 426 Expect().Status(200). 427 Body().IsEqual("<svg>...</svg>") 428 }) 429 430 t.Run("DownloadApp", func(t *testing.T) { 431 e := testutils.CreateTestClient(t, ts.URL) 432 433 res := e.GET("/apps/mini/download"). 434 WithHost(testInstance.Domain). 435 WithHeader("Authorization", "Bearer "+token). 436 Expect().Status(200). 437 Raw() 438 439 mimeType, reader := filetype.FromReader(res.Body) 440 require.Equal(t, "application/gzip", mimeType) 441 gr, err := gzip.NewReader(reader) 442 require.NoError(t, err) 443 tr := tar.NewReader(gr) 444 indexFound := false 445 for { 446 header, err := tr.Next() 447 if errors.Is(err, io.EOF) { 448 break 449 } 450 require.NoError(t, err) 451 if header.Name == "/index.html" { 452 indexFound = true 453 } 454 } 455 require.True(t, indexFound) 456 }) 457 458 t.Run("DownloadKonnectorVersion", func(t *testing.T) { 459 e := testutils.CreateTestClient(t, ts.URL) 460 461 res := e.GET("/konnectors/mini/download/1.0.0"). 462 WithHost(testInstance.Domain). 463 WithHeader("Authorization", "Bearer "+token). 464 Expect().Status(200). 465 Raw() 466 467 mimeType, reader := filetype.FromReader(res.Body) 468 require.Equal(t, "application/gzip", mimeType) 469 gr, err := gzip.NewReader(reader) 470 require.NoError(t, err) 471 tr := tar.NewReader(gr) 472 iconFound := false 473 for { 474 header, err := tr.Next() 475 if errors.Is(err, io.EOF) { 476 break 477 } 478 require.NoError(t, err) 479 if header.Name == "/icon.svg" { 480 iconFound = true 481 } 482 } 483 require.True(t, iconFound) 484 }) 485 486 t.Run("OpenWebapp", func(t *testing.T) { 487 e := testutils.CreateTestClient(t, ts.URL) 488 489 // Expected flags since they can be modified by other tests 490 flags, err := feature.GetFlags(testInstance) 491 require.NoError(t, err) 492 flagsStr, err := json.Marshal(flags) 493 require.NoError(t, err) 494 495 // Create the OAuth client for the flagship app 496 flagship := oauth.Client{ 497 RedirectURIs: []string{"cozy://flagship"}, 498 ClientName: "flagship-app", 499 SoftwareID: "github.com/cozy/cozy-stack/testing/flagship", 500 Flagship: true, 501 } 502 require.Nil(t, flagship.Create(testInstance, oauth.NotPending)) 503 504 // Create a maximal permission for it 505 token, err := testInstance.MakeJWT(consts.AccessTokenAudience, 506 flagship.ClientID, "*", "", time.Now()) 507 require.NoError(t, err) 508 509 obj := e.GET("/apps/mini/open"). 510 WithHost(testInstance.Domain). 511 WithHeader("Authorization", "Bearer "+token). 512 Expect().Status(200). 513 JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). 514 Object() 515 516 data := obj.Value("data").Object() 517 data.Value("id").String().NotEmpty() 518 data.HasValue("type", "io.cozy.apps.open") 519 520 attrs := data.Value("attributes").Object() 521 attrs.HasValue("AppName", "Mini") 522 attrs.HasValue("AppSlug", "mini") 523 attrs.HasValue("IconPath", "icon.svg") 524 attrs.HasValue("Tracking", "false") 525 attrs.HasValue("SubDomain", "flat") 526 attrs.Value("Cookie").String().Contains("HttpOnly") 527 attrs.Value("Token").String().NotEmpty() 528 attrs.HasValue("Flags", string(flagsStr)) 529 attrs.ContainsKey("Warnings") 530 531 links := data.Value("links").Object() 532 links.HasValue("self", "/apps/mini/open") 533 }) 534 535 t.Run("UninstallAppWithLinkedClient", func(t *testing.T) { 536 e := testutils.CreateTestClient(t, ts.URL) 537 538 // Install drive app 539 installer, err := apps.NewInstaller(testInstance, apps.Copier(consts.WebappType, testInstance), 540 &apps.InstallerOptions{ 541 Operation: apps.Install, 542 Type: consts.WebappType, 543 Slug: "drive", 544 SourceURL: "registry://drive", 545 Registries: testInstance.Registries(), 546 }, 547 ) 548 require.NoError(t, err) 549 550 _, err = installer.RunSync() 551 require.NoError(t, err) 552 553 // Link an OAuthClient to drive 554 oauthClient := &oauth.Client{ 555 ClientName: "test-linked", 556 RedirectURIs: []string{"https://foobar"}, 557 SoftwareID: "registry://drive", 558 } 559 560 oauthClient.Create(testInstance) 561 // Forcing the oauthClient to get a couchID for the purpose of later deletion 562 oauthClient, err = oauth.FindClient(testInstance, oauthClient.ClientID) 563 require.NoError(t, err) 564 565 scope := "io.cozy.apps:ALL" 566 token, err := testInstance.MakeJWT("cli", "drive", scope, "", time.Now()) 567 require.NoError(t, err) 568 569 // Trying to remove this app 570 e.DELETE("/apps/drive"). 571 WithHost(testInstance.Domain). 572 WithHeader("Authorization", "Bearer "+token). 573 Expect().Status(400). 574 Body().Contains("linked OAuth client exists") 575 576 // Cleaning 577 uninstaller, err := apps.NewInstaller(testInstance, apps.Copier(consts.WebappType, testInstance), 578 &apps.InstallerOptions{ 579 Operation: apps.Delete, 580 Type: consts.WebappType, 581 Slug: "drive", 582 SourceURL: "registry://drive", 583 Registries: testInstance.Registries(), 584 }, 585 ) 586 assert.NoError(t, err) 587 _, err = uninstaller.RunSync() 588 assert.NoError(t, err) 589 errc := oauthClient.Delete(testInstance) 590 assert.Nil(t, errc) 591 }) 592 593 t.Run("UninstallAppWithoutLinkedClient", func(t *testing.T) { 594 e := testutils.CreateTestClient(t, ts.URL) 595 596 // Install drive app 597 installer, err := apps.NewInstaller(testInstance, apps.Copier(consts.WebappType, testInstance), 598 &apps.InstallerOptions{ 599 Operation: apps.Install, 600 Type: consts.WebappType, 601 Slug: "drive", 602 SourceURL: "registry://drive", 603 Registries: testInstance.Registries(), 604 }, 605 ) 606 require.NoError(t, err) 607 608 _, err = installer.RunSync() 609 require.NoError(t, err) 610 611 // Create an OAuth client not linked to drive 612 oauthClient := &oauth.Client{ 613 ClientName: "test-linked", 614 RedirectURIs: []string{"https://foobar"}, 615 SoftwareID: "foobarclient", 616 } 617 oauthClient.Create(testInstance) 618 // Forcing the oauthClient to get a couchID for the purpose of later deletion 619 oauthClient, err = oauth.FindClient(testInstance, oauthClient.ClientID) 620 require.NoError(t, err) 621 622 scope := "io.cozy.apps:ALL" 623 token, err := testInstance.MakeJWT("cli", "drive", scope, "", time.Now()) 624 assert.NoError(t, err) 625 626 // Trying to remove this app 627 e.DELETE("/apps/drive"). 628 WithHost(testInstance.Domain). 629 WithHeader("Authorization", "Bearer "+token). 630 Expect().Status(200) 631 632 // Cleaning 633 errc := oauthClient.Delete(testInstance) 634 assert.Nil(t, errc) 635 }) 636 637 t.Run("SendKonnectorLogsFromFlagshipApp", func(t *testing.T) { 638 e := testutils.CreateTestClient(t, ts.URL) 639 640 initialOutput := logrus.New().Out 641 defer logrus.SetOutput(initialOutput) 642 643 testOutput := new(bytes.Buffer) 644 logrus.SetOutput(testOutput) 645 646 // Create the OAuth client for the flagship app 647 flagship := oauth.Client{ 648 RedirectURIs: []string{"cozy://flagship"}, 649 ClientName: "flagship-app", 650 SoftwareID: "github.com/cozy/cozy-stack/testing/flagship", 651 Flagship: true, 652 } 653 require.Nil(t, flagship.Create(testInstance, oauth.NotPending)) 654 655 // Give it the maximal permission 656 token, err := testInstance.MakeJWT(consts.AccessTokenAudience, flagship.ClientID, "*", "", time.Now()) 657 require.NoError(t, err) 658 659 // Send logs for a client side konnector 660 e.POST("/konnectors/"+clientSideSlug+"/logs"). 661 WithHost(testInstance.Domain). 662 WithHeader("Authorization", "Bearer "+token). 663 WithBytes([]byte(`[ { "timestamp": "2022-10-27T17:13:38.382Z", "level": "error", "msg": "This is an error message" } ]`)). 664 Expect().Status(204) 665 666 assert.Equal(t, `time="2022-10-27T17:13:38.382Z" level=error msg="This is an error message" domain=`+domain+" job_id= nspace=jobs slug="+clientSideSlug+" worker_id=client\n", testOutput.String()) 667 668 // Send logs for a konnector 669 testOutput.Reset() 670 e.POST("/konnectors/"+konnectorSlug+"/logs"). 671 WithHost(testInstance.Domain). 672 WithHeader("Authorization", "Bearer "+token). 673 WithBytes([]byte(`[ { "timestamp": "2022-10-27T17:13:38.382Z", "level": "error", "msg": "This is an error message" } ]`)). 674 Expect().Status(204) 675 676 assert.Equal(t, `time="2022-10-27T17:13:38.382Z" level=error msg="This is an error message" domain=`+domain+" job_id= nspace=jobs slug="+konnectorSlug+"\n", testOutput.String()) 677 678 // Send logs for a webapp 679 testOutput.Reset() 680 e.POST("/apps/"+slug+"/logs"). 681 WithHost(testInstance.Domain). 682 WithHeader("Authorization", "Bearer "+token). 683 WithBytes([]byte(`[ { "timestamp": "2022-10-27T17:13:38.382Z", "level": "error", "msg": "This is an error message" } ]`)). 684 Expect().Status(204) 685 686 assert.Equal(t, `time="2022-10-27T17:13:38.382Z" level=error msg="This is an error message" domain=`+domain+" job_id= nspace=jobs slug="+slug+"\n", testOutput.String()) 687 }) 688 689 t.Run("SendKonnectorLogsFromKonnector", func(t *testing.T) { 690 e := testutils.CreateTestClient(t, ts.URL) 691 692 initialOutput := logrus.New().Out 693 defer logrus.SetOutput(initialOutput) 694 695 testOutput := new(bytes.Buffer) 696 logrus.SetOutput(testOutput) 697 698 token := testInstance.BuildKonnectorToken(slug) 699 700 e.POST("/konnectors/"+slug+"/logs"). 701 WithHost(testInstance.Domain). 702 WithHeader("Authorization", "Bearer "+token). 703 WithBytes([]byte(`[ { "timestamp": "2022-10-27T17:13:38.382Z", "level": "error", "msg": "This is an error message" } ]`)). 704 Expect().Status(204) 705 706 assert.Equal(t, `time="2022-10-27T17:13:38.382Z" level=error msg="This is an error message" domain=`+domain+" job_id= nspace=jobs slug="+slug+"\n", testOutput.String()) 707 708 // Sending logs for a webapp should fail 709 e.POST("/apps/"+slug+"/logs"). 710 WithHost(testInstance.Domain). 711 WithHeader("Authorization", "Bearer "+token). 712 WithBytes([]byte(`[ { "timestamp": "2022-10-27T17:13:38.382Z", "level": "error", "msg": "This is an error message" } ]`)). 713 Expect().Status(403) 714 }) 715 716 t.Run("SendAppLogsFromWebApp", func(t *testing.T) { 717 e := testutils.CreateTestClient(t, ts.URL) 718 719 initialOutput := logrus.New().Out 720 defer logrus.SetOutput(initialOutput) 721 722 testOutput := new(bytes.Buffer) 723 logrus.SetOutput(testOutput) 724 725 token := testInstance.BuildAppToken(slug, "") 726 727 e.POST("/apps/"+slug+"/logs"). 728 WithHost(testInstance.Domain). 729 WithHeader("Authorization", "Bearer "+token). 730 WithBytes([]byte(`[ { "timestamp": "2022-10-27T17:13:38.382Z", "level": "error", "msg": "This is an error message" } ]`)). 731 Expect().Status(204) 732 733 assert.Equal(t, `time="2022-10-27T17:13:38.382Z" level=error msg="This is an error message" domain=`+domain+" job_id= nspace=jobs slug="+slug+"\n", testOutput.String()) 734 735 // Sending logs for a konnector should fail 736 e.POST("/konnectors/"+slug+"/logs"). 737 WithHost(testInstance.Domain). 738 WithHeader("Authorization", "Bearer "+token). 739 WithBytes([]byte(`[ { "timestamp": "2022-10-27T17:13:38.382Z", "level": "error", "msg": "This is an error message" } ]`)). 740 Expect().Status(403) 741 }) 742 } 743 744 func assertAuthGet(e *httpexpect.Expect, slug, domain, path, contentType, charset, content string) { 745 e.GET(path). 746 WithHost(slug+"."+domain). 747 Expect().Status(200). 748 HasContentType(contentType, charset). 749 Body().Contains(content) 750 } 751 752 func assertAnonGet(e *httpexpect.Expect, slug, domain, path, contentType, charset, content string) { 753 e.GET(path). 754 WithHost(slug+"."+domain). 755 Expect().Status(200). 756 HasContentType(contentType, charset). 757 Body().Contains(content) 758 } 759 760 func assertNotPublic(e *httpexpect.Expect, slug, domain, path string, code int, location string) { 761 e.GET(path). 762 WithHost(slug + "." + domain). 763 WithRedirectPolicy(httpexpect.DontFollowRedirects). 764 Expect().Status(code). 765 Header("Location").IsEqual(location) 766 } 767 768 func assertNotFound(e *httpexpect.Expect, slug, domain, path string) { 769 e.GET(path). 770 WithHost(slug + "." + domain). 771 WithRedirectPolicy(httpexpect.DontFollowRedirects). 772 Expect().Status(404) 773 } 774 775 func assertInternalServerError(e *httpexpect.Expect, slug, domain, path string) { 776 e.GET(path). 777 WithHost(slug + "." + domain). 778 Expect().Status(500) 779 } 780 781 func assertRedirect(e *httpexpect.Expect, slug, domain, path string, code int, location string) { 782 e.GET(path). 783 WithHost(slug + "." + domain). 784 WithRedirectPolicy(httpexpect.DontFollowRedirects). 785 Expect().Status(code). 786 Header("Location").IsEqual(location) 787 }