github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/sys/it/impl_childworkspace_test.go (about) 1 /* 2 * Copyright (c) 2020-present unTill Pro, Ltd. 3 */ 4 5 package sys_it 6 7 import ( 8 "fmt" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/require" 13 "github.com/voedger/voedger/pkg/appdef" 14 "github.com/voedger/voedger/pkg/iauthnz" 15 "github.com/voedger/voedger/pkg/iauthnzimpl" 16 "github.com/voedger/voedger/pkg/istructs" 17 payloads "github.com/voedger/voedger/pkg/itokens-payloads" 18 coreutils "github.com/voedger/voedger/pkg/utils" 19 it "github.com/voedger/voedger/pkg/vit" 20 ) 21 22 func TestBasicUsage_ChildWorkspaces(t *testing.T) { 23 require := require.New(t) 24 vit := it.NewVIT(t, &it.SharedConfig_App1) 25 defer vit.TearDown() 26 27 parentWS := vit.WS(istructs.AppQName_test1_app1, "test_ws") 28 wsName := vit.NextName() 29 30 t.Run("404 not found on unexisting child workspace query", func(t *testing.T) { 31 body := fmt.Sprintf(` 32 { 33 "args": { 34 "WSName": "%s" 35 }, 36 "elements":[ 37 { 38 "fields":["WSID"] 39 } 40 ] 41 }`, wsName) 42 resp := vit.PostWS(parentWS, "q.sys.QueryChildWorkspaceByName", body, coreutils.Expect404()) 43 resp.Println() 44 }) 45 46 t.Run("create child workspace", func(t *testing.T) { 47 // init 48 // note: creating workspace at non-main cluster is unsupported for now 49 // because there are no AppWorkspaces in any cluster but Main (created automatically on VVM launch) 50 // also GetAppWSID() uses MainCluser, not the target one 51 body := fmt.Sprintf(` 52 { 53 "args": { 54 "WSName": "%s", 55 "WSKind": "app1pkg.test_ws", 56 "WSKindInitializationData": "{\"IntFld\": 10}", 57 "TemplateName": "test_template", 58 "WSClusterID": 1 59 } 60 }`, wsName) 61 vit.PostWS(parentWS, "c.sys.InitChildWorkspace", body) 62 63 // wait for finish 64 childWS := vit.WaitForChildWorkspace(parentWS, wsName) 65 require.Empty(childWS.WSError) 66 require.Equal(wsName, childWS.Name) 67 require.Equal(it.QNameApp1_TestWSKind, childWS.Kind) 68 require.Equal(`{"IntFld": 10}`, childWS.InitDataJSON) 69 require.Equal("test_template", childWS.TemplateName) 70 require.Equal(istructs.ClusterID(1), childWS.WSID.ClusterID()) 71 72 t.Run("create a new workspace with an existing name -> 409 conflict", func(t *testing.T) { 73 body := fmt.Sprintf(`{"args": {"WSName": "%s","WSKind": "app1pkg.test_ws","WSKindInitializationData": "{\"WorkStartTime\": \"10\"}","TemplateName": "test","WSClusterID": 1}}`, wsName) 74 resp := vit.PostWS(parentWS, "c.sys.InitChildWorkspace", body, coreutils.Expect409()) 75 resp.Println() 76 }) 77 }) 78 79 t.Run("read child workspaces list", func(t *testing.T) { 80 body := `{"args":{"Schema":"sys.ChildWorkspace"},"elements":[{"fields":["WSName","WSKind","WSID","WSError"]}]}` 81 resp := vit.PostWS(parentWS, "q.sys.Collection", body) 82 // note: wsKind is rendered as {} because q.sys.Collection appends QName to the object to marshal to JSON by value 83 // whereas appdef.QName.MarshalJSON() func has pointer receiver 84 resp.Println() 85 }) 86 } 87 88 func TestForeignAuthorization(t *testing.T) { 89 require := require.New(t) 90 vit := it.NewVIT(t, &it.SharedConfig_App1) 91 defer vit.TearDown() 92 93 // sign up a new login 94 newLoginName := vit.NextName() 95 newLogin := vit.SignUp(newLoginName, "1", istructs.AppQName_test1_app1) 96 newPrn := vit.SignIn(newLogin) 97 98 parentWS := vit.WS(istructs.AppQName_test1_app1, "test_ws") 99 wsName := vit.NextName() 100 101 // init child workspace 102 body := fmt.Sprintf(`{"args": {"WSName": "%s","WSKind": "app1pkg.test_ws","WSKindInitializationData": "{\"IntFld\": 10}","TemplateName": "test_template","WSClusterID": 42}}`, wsName) 103 vit.PostWS(parentWS, "c.sys.InitChildWorkspace", body) 104 105 // wait for finish 106 childWS := vit.WaitForChildWorkspace(parentWS, wsName) 107 108 t.Run("subjects", func(t *testing.T) { 109 // try to execute an operation by the foreign login, expect 403 110 cudBody := `{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "app1pkg.articles","name": "cola","article_manual": 1,"article_hash": 2,"hideonhold": 3,"time_active": 4,"control_active": 5}}]}` 111 vit.PostWS(parentWS, "c.sys.CUD", cudBody, coreutils.Expect403(), coreutils.WithAuthorizeBy(newPrn.Token)) 112 113 // make this new foreign login a subject in the existing workspace 114 body := fmt.Sprintf(`{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "sys.Subject","Login": "%s","SubjectKind":%d,"Roles": "%s","ProfileWSID":%d}}]}`, 115 newLoginName, istructs.SubjectKind_User, iauthnz.QNameRoleWorkspaceOwner, newPrn.ProfileWSID) 116 vit.PostWS(parentWS, "c.sys.CUD", body) 117 118 // now the foreign login could work in the workspace 119 vit.PostWS(parentWS, "c.sys.CUD", cudBody, coreutils.WithAuthorizeBy(newPrn.Token)) 120 }) 121 122 t.Run("enrich principal token", func(t *testing.T) { 123 // 403 forbidden on try to execute a stricted operation in the child workspace using the non-enriched token 124 body = `{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "app1pkg.articles","name": "cola","article_manual": 1,"article_hash": 2,"hideonhold": 3,"time_active": 4,"control_active": 5}}]}` 125 vit.PostWS(childWS, "c.sys.CUD", body, coreutils.Expect403()) 126 127 // create cdoc.sys.Subject with a role the custom func execution could be authorized with 128 body = fmt.Sprintf(`{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "sys.Subject","Login": "login","SubjectKind":%d,"Roles": "%s","ProfileWSID":%d}}]}`, 129 istructs.SubjectKind_User, iauthnz.QNameRoleWorkspaceOwner, newPrn.ProfileWSID) 130 vit.PostWS(parentWS, "c.sys.CUD", body) 131 132 // enrich the principal token in the parentWS 133 // basic auth 134 body = `{"args":{"Login":"login"},"elements":[{"fields":["EnrichedToken"]}]}` 135 resp := vit.PostWS(parentWS, "q.sys.EnrichPrincipalToken", body) 136 enrichedToken := resp.SectionRow()[0].(string) 137 138 // ok to execute a stricted operation in the child workspace using the enriched token 139 // expect no errors 140 body = `{"cuds": [{"fields": {"sys.ID": 1,"sys.QName": "app1pkg.articles","name": "cola","article_manual": 1,"article_hash": 2,"hideonhold": 3,"time_active": 4,"control_active": 5}}]}` 141 vit.PostWS(childWS, "c.sys.CUD", body, coreutils.WithAuthorizeBy(enrichedToken)) 142 }) 143 144 t.Run("API token", func(t *testing.T) { 145 // 403 forbidden on try to execute a stricted operation in the child workspace 146 body = `{"args":{"Schema":"app1pkg.articles"},"elements":[{"fields":["sys.ID"]}]}` 147 vit.PostWS(childWS, "q.sys.Collection", body, coreutils.Expect403()) 148 149 // issue an API token 150 as, err := vit.IAppStructsProvider.AppStructs(istructs.AppQName_test1_app1) 151 require.NoError(err) 152 apiToken, err := iauthnzimpl.IssueAPIToken(as.AppTokens(), time.Hour, []appdef.QName{appdef.NewQName("air", "AirReseller")}, childWS.WSID, payloads.PrincipalPayload{ 153 Login: parentWS.Owner.Name, 154 SubjectKind: istructs.SubjectKind_User, 155 ProfileWSID: parentWS.Owner.ProfileWSID, 156 }) 157 require.NoError(err) 158 159 // API token has role.air.AirReseller, q.sys.Collection is allowed for that role according to the current ACL -> the request should be successful 160 vit.PostWS(childWS, "q.sys.Collection", body, coreutils.WithAuthorizeBy(apiToken)) 161 }) 162 }