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  }