@@ -1463,6 +1463,151 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
1463
1463
require .Equal (t ,http .StatusOK ,resp .StatusCode )
1464
1464
assertWorkspaceLastUsedAtUpdated (t ,appDetails )
1465
1465
})
1466
+
1467
+ t .Run ("CORS" ,func (t * testing.T ) {
1468
+ t .Parallel ()
1469
+
1470
+ // Set up test headers that should be returned by the app
1471
+ testHeaders := http.Header {
1472
+ "Access-Control-Allow-Origin" : []string {"*" },
1473
+ "Access-Control-Allow-Methods" : []string {"GET, POST, OPTIONS" },
1474
+ }
1475
+
1476
+ unauthenticatedClient := func (t * testing.T ,appDetails * Details )* codersdk.Client {
1477
+ c := appDetails .AppClient (t )
1478
+ c .SetSessionToken ("" )
1479
+ return c
1480
+ }
1481
+
1482
+ authenticatedClient := func (t * testing.T ,appDetails * Details )* codersdk.Client {
1483
+ uc ,_ := coderdtest .CreateAnotherUser (t ,appDetails .SDKClient ,appDetails .FirstUser .OrganizationID ,rbac .RoleMember ())
1484
+ c := appDetails .AppClient (t )
1485
+ c .SetSessionToken (uc .SessionToken ())
1486
+ return c
1487
+ }
1488
+
1489
+ ownerClient := func (t * testing.T ,appDetails * Details )* codersdk.Client {
1490
+ return appDetails .SDKClient
1491
+ }
1492
+
1493
+ tests := []struct {
1494
+ name string
1495
+ shareLevel codersdk.WorkspaceAgentPortShareLevel
1496
+ behavior codersdk.AppCORSBehavior
1497
+ client func (t * testing.T ,appDetails * Details )* codersdk.Client
1498
+ expectedStatusCode int
1499
+ expectedCORSHeaders bool
1500
+ }{
1501
+ // Public
1502
+ {
1503
+ name :"Default/Public" ,
1504
+ shareLevel :codersdk .WorkspaceAgentPortShareLevelPublic ,
1505
+ behavior :codersdk .AppCORSBehaviorSimple ,
1506
+ expectedCORSHeaders :false ,
1507
+ client :unauthenticatedClient ,
1508
+ expectedStatusCode :http .StatusOK ,
1509
+ },
1510
+ {
1511
+ name :"Passthru/Public" ,
1512
+ shareLevel :codersdk .WorkspaceAgentPortShareLevelPublic ,
1513
+ behavior :codersdk .AppCORSBehaviorPassthru ,
1514
+ expectedCORSHeaders :true ,
1515
+ client :unauthenticatedClient ,
1516
+ expectedStatusCode :http .StatusOK ,
1517
+ },
1518
+ // Authenticated
1519
+ {
1520
+ name :"Default/Authenticated" ,
1521
+ shareLevel :codersdk .WorkspaceAgentPortShareLevelAuthenticated ,
1522
+ behavior :codersdk .AppCORSBehaviorSimple ,
1523
+ expectedCORSHeaders :false ,
1524
+ client :authenticatedClient ,
1525
+ expectedStatusCode :http .StatusOK ,
1526
+ },
1527
+ {
1528
+ name :"Passthru/Authenticated" ,
1529
+ shareLevel :codersdk .WorkspaceAgentPortShareLevelAuthenticated ,
1530
+ behavior :codersdk .AppCORSBehaviorPassthru ,
1531
+ expectedCORSHeaders :true ,
1532
+ client :authenticatedClient ,
1533
+ expectedStatusCode :http .StatusOK ,
1534
+ },
1535
+ {
1536
+ // The CORS behavior will not affect unauthenticated requests.
1537
+ // The request will be redirected to the login page.
1538
+ name :"Passthru/Unauthenticated" ,
1539
+ shareLevel :codersdk .WorkspaceAgentPortShareLevelAuthenticated ,
1540
+ behavior :codersdk .AppCORSBehaviorPassthru ,
1541
+ expectedCORSHeaders :false ,
1542
+ client :unauthenticatedClient ,
1543
+ expectedStatusCode :http .StatusSeeOther ,
1544
+ },
1545
+ // Owner
1546
+ {
1547
+ name :"Default/Owner" ,
1548
+ shareLevel :codersdk .WorkspaceAgentPortShareLevelAuthenticated ,// Owner is not a valid share level for ports.
1549
+ behavior :codersdk .AppCORSBehaviorSimple ,
1550
+ expectedCORSHeaders :false ,
1551
+ client :ownerClient ,
1552
+ expectedStatusCode :http .StatusOK ,
1553
+ },
1554
+ {
1555
+ name :"Passthru/Owner" ,
1556
+ shareLevel :codersdk .WorkspaceAgentPortShareLevelAuthenticated ,// Owner is not a valid share level for ports.
1557
+ behavior :codersdk .AppCORSBehaviorPassthru ,
1558
+ expectedCORSHeaders :true ,
1559
+ client :ownerClient ,
1560
+ expectedStatusCode :http .StatusOK ,
1561
+ },
1562
+ }
1563
+
1564
+ for _ ,tc := range tests {
1565
+ t .Run (tc .name ,func (t * testing.T ) {
1566
+ t .Parallel ()
1567
+
1568
+ ctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitLong )
1569
+ defer cancel ()
1570
+
1571
+ appDetails := setupProxyTest (t ,& DeploymentOptions {
1572
+ headers :testHeaders ,
1573
+ })
1574
+ port ,err := strconv .ParseInt (appDetails .Apps .Port .AppSlugOrPort ,10 ,32 )
1575
+ require .NoError (t ,err )
1576
+
1577
+ // Update the template CORS behavior.
1578
+ b := codersdk .AppCORSBehavior (tc .behavior )
1579
+ template ,err := appDetails .SDKClient .UpdateTemplateMeta (ctx ,appDetails .Workspace .TemplateID , codersdk.UpdateTemplateMeta {
1580
+ CORSBehavior :& b ,
1581
+ })
1582
+ require .NoError (t ,err )
1583
+ require .Equal (t ,tc .behavior ,template .CORSBehavior )
1584
+
1585
+ // Set the port we have to be shared.
1586
+ _ ,err = appDetails .SDKClient .UpsertWorkspaceAgentPortShare (ctx ,appDetails .Workspace .ID , codersdk.UpsertWorkspaceAgentPortShareRequest {
1587
+ AgentName :proxyTestAgentName ,
1588
+ Port :int32 (port ),
1589
+ ShareLevel :tc .shareLevel ,
1590
+ Protocol :codersdk .WorkspaceAgentPortShareProtocolHTTP ,
1591
+ })
1592
+ require .NoError (t ,err )
1593
+
1594
+ client := tc .client (t ,appDetails )
1595
+
1596
+ resp ,err := requestWithRetries (ctx ,t ,client ,http .MethodGet ,appDetails .SubdomainAppURL (appDetails .Apps .Port ).String (),nil )
1597
+ require .NoError (t ,err )
1598
+ defer resp .Body .Close ()
1599
+ require .Equal (t ,tc .expectedStatusCode ,resp .StatusCode )
1600
+
1601
+ if tc .expectedCORSHeaders {
1602
+ require .Equal (t ,testHeaders .Get ("Access-Control-Allow-Origin" ),resp .Header .Get ("Access-Control-Allow-Origin" ))
1603
+ require .Equal (t ,testHeaders .Get ("Access-Control-Allow-Methods" ),resp .Header .Get ("Access-Control-Allow-Methods" ))
1604
+ }else {
1605
+ require .Empty (t ,resp .Header .Get ("Access-Control-Allow-Origin" ))
1606
+ require .Empty (t ,resp .Header .Get ("Access-Control-Allow-Methods" ))
1607
+ }
1608
+ })
1609
+ }
1610
+ })
1466
1611
})
1467
1612
1468
1613
t .Run ("AppSharing" ,func (t * testing.T ) {