github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/ee/acl/acl_test.go (about) 1 // +build !oss 2 3 /* 4 * Copyright 2018 Dgraph Labs, Inc. and Contributors 5 * 6 * Licensed under the Dgraph Community License (the "License"); you 7 * may not use this file except in compliance with the License. You 8 * may obtain a copy of the License at 9 * 10 * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt 11 */ 12 13 package acl 14 15 import ( 16 "context" 17 "fmt" 18 "os" 19 "os/exec" 20 "path/filepath" 21 "strconv" 22 "testing" 23 "time" 24 25 "github.com/dgraph-io/dgo" 26 "github.com/dgraph-io/dgo/protos/api" 27 "github.com/dgraph-io/dgraph/testutil" 28 "github.com/dgraph-io/dgraph/x" 29 "github.com/golang/glog" 30 "github.com/stretchr/testify/require" 31 ) 32 33 var ( 34 userid = "alice" 35 userpassword = "simplepassword" 36 dgraphEndpoint = testutil.SockAddr 37 ) 38 39 func checkOutput(t *testing.T, cmd *exec.Cmd, shouldFail bool) string { 40 out, err := cmd.CombinedOutput() 41 if (!shouldFail && err != nil) || (shouldFail && err == nil) { 42 t.Errorf("Error output from command:%v", string(out)) 43 t.Fatal(err) 44 } 45 46 return string(out) 47 } 48 49 func TestCreateAndDeleteUsers(t *testing.T) { 50 // clean up the user to allow repeated running of this test 51 cleanUserCmd := exec.Command("dgraph", "acl", "del", "-a", dgraphEndpoint, 52 "-u", userid, "-x", "password") 53 cleanUserCmd.Run() 54 glog.Infof("cleaned up db user state") 55 56 createUserCmd1 := exec.Command("dgraph", "acl", "add", "-a", dgraphEndpoint, "-u", userid, 57 "-p", userpassword, "-x", "password") 58 checkOutput(t, createUserCmd1, false) 59 60 createUserCmd2 := exec.Command("dgraph", "acl", "add", "-a", dgraphEndpoint, "-u", userid, 61 "-p", userpassword, "-x", "password") 62 // create the user again should fail 63 checkOutput(t, createUserCmd2, true) 64 65 // delete the user 66 deleteUserCmd := exec.Command("dgraph", "acl", "del", "-a", dgraphEndpoint, "-u", userid, 67 "-x", "password") 68 checkOutput(t, deleteUserCmd, false) 69 70 // now we should be able to create the user again 71 createUserCmd3 := exec.Command("dgraph", "acl", "add", "-a", dgraphEndpoint, "-u", userid, 72 "-p", userpassword, "-x", "password") 73 checkOutput(t, createUserCmd3, false) 74 } 75 76 func resetUser(t *testing.T) { 77 // delete and recreate the user to ensure a clean state 78 deleteUserCmd := exec.Command("dgraph", "acl", "del", "-a", dgraphEndpoint, 79 "-u", userid, "-x", "password") 80 deleteUserCmd.Run() 81 glog.Infof("deleted user") 82 83 createUserCmd := exec.Command("dgraph", "acl", "add", "-a", dgraphEndpoint, "-u", 84 userid, "-p", userpassword, "-x", "password") 85 checkOutput(t, createUserCmd, false) 86 glog.Infof("created user") 87 } 88 89 func TestReservedPredicates(t *testing.T) { 90 // This test uses the groot account to ensure that reserved predicates 91 // cannot be altered even if the permissions allow it. 92 ctx := context.Background() 93 94 dg1 := testutil.DgraphClientWithGroot(testutil.SockAddr) 95 alterReservedPredicates(t, dg1) 96 97 dg2 := testutil.DgraphClientWithGroot(testutil.SockAddr) 98 if err := dg2.Login(ctx, x.GrootId, "password"); err != nil { 99 t.Fatalf("unable to login using the groot account:%v", err) 100 } 101 alterReservedPredicates(t, dg2) 102 } 103 104 func TestAuthorization(t *testing.T) { 105 if testing.Short() { 106 t.Skip("skipping because -short=true") 107 } 108 109 glog.Infof("testing with port 9180") 110 dg1 := testutil.DgraphClientWithGroot(testutil.SockAddr) 111 testAuthorization(t, dg1) 112 glog.Infof("done") 113 114 glog.Infof("testing with port 9182") 115 dg2 := testutil.DgraphClientWithGroot(":9182") 116 testAuthorization(t, dg2) 117 glog.Infof("done") 118 } 119 120 func testAuthorization(t *testing.T, dg *dgo.Dgraph) { 121 createAccountAndData(t, dg) 122 ctx := context.Background() 123 if err := dg.Login(ctx, userid, userpassword); err != nil { 124 t.Fatalf("unable to login using the account %v", userid) 125 } 126 127 // initially the query, mutate and alter operations should all succeed 128 // when there are no rules defined on the predicates (the fail open approach) 129 queryPredicateWithUserAccount(t, dg, false) 130 mutatePredicateWithUserAccount(t, dg, false) 131 alterPredicateWithUserAccount(t, dg, false) 132 createGroupAndAcls(t, unusedGroup, false) 133 // wait for 6 seconds to ensure the new acl have reached all acl caches 134 glog.Infof("Sleeping for 6 seconds for acl caches to be refreshed") 135 time.Sleep(6 * time.Second) 136 137 // now all these operations should fail since there are rules defined on the unusedGroup 138 queryPredicateWithUserAccount(t, dg, true) 139 mutatePredicateWithUserAccount(t, dg, true) 140 alterPredicateWithUserAccount(t, dg, true) 141 // create the dev group and add the user to it 142 createGroupAndAcls(t, devGroup, true) 143 144 // wait for 6 seconds to ensure the new acl have reached all acl caches 145 glog.Infof("Sleeping for 6 seconds for acl caches to be refreshed") 146 time.Sleep(6 * time.Second) 147 148 // now the operations should succeed again through the devGroup 149 queryPredicateWithUserAccount(t, dg, false) 150 // sleep long enough (10s per the docker-compose.yml) 151 // for the accessJwt to expire in order to test auto login through refresh jwt 152 glog.Infof("Sleeping for 4 seconds for accessJwt to expire") 153 time.Sleep(4 * time.Second) 154 mutatePredicateWithUserAccount(t, dg, false) 155 glog.Infof("Sleeping for 4 seconds for accessJwt to expire") 156 time.Sleep(4 * time.Second) 157 alterPredicateWithUserAccount(t, dg, false) 158 } 159 160 var predicateToRead = "predicate_to_read" 161 var queryAttr = "name" 162 var predicateToWrite = "predicate_to_write" 163 var predicateToAlter = "predicate_to_alter" 164 var devGroup = "dev" 165 var unusedGroup = "unusedGroup" 166 var rootDir = filepath.Join(os.TempDir(), "acl_test") 167 var query = fmt.Sprintf(` 168 { 169 q(func: eq(%s, "SF")) { 170 %s 171 } 172 }`, predicateToRead, queryAttr) 173 174 func alterReservedPredicates(t *testing.T, dg *dgo.Dgraph) { 175 ctx := context.Background() 176 177 // Test that alter requests are allowed if the new update is the same as 178 // the initial update for a reserved predicate. 179 err := dg.Alter(ctx, &api.Operation{ 180 Schema: "dgraph.xid: string @index(exact) @upsert .", 181 }) 182 require.NoError(t, err) 183 184 err = dg.Alter(ctx, &api.Operation{ 185 Schema: "dgraph.xid: int .", 186 }) 187 require.Error(t, err) 188 require.Contains(t, err.Error(), 189 "predicate dgraph.xid is reserved and is not allowed to be modified") 190 191 err = dg.Alter(ctx, &api.Operation{ 192 DropAttr: "dgraph.xid", 193 }) 194 require.Error(t, err) 195 require.Contains(t, err.Error(), 196 "predicate dgraph.xid is reserved and is not allowed to be dropped") 197 198 // Test that reserved predicates act as case-insensitive. 199 err = dg.Alter(ctx, &api.Operation{ 200 Schema: "dgraph.XID: int .", 201 }) 202 require.Error(t, err) 203 require.Contains(t, err.Error(), 204 "predicate dgraph.XID is reserved and is not allowed to be modified") 205 } 206 207 func queryPredicateWithUserAccount(t *testing.T, dg *dgo.Dgraph, shouldFail bool) { 208 // login with alice's account 209 ctx := context.Background() 210 txn := dg.NewTxn() 211 _, err := txn.Query(ctx, query) 212 213 if shouldFail { 214 require.Error(t, err, "the query should have failed") 215 } else { 216 require.NoError(t, err, "the query should have succeeded") 217 } 218 } 219 220 func mutatePredicateWithUserAccount(t *testing.T, dg *dgo.Dgraph, shouldFail bool) { 221 ctx := context.Background() 222 txn := dg.NewTxn() 223 _, err := txn.Mutate(ctx, &api.Mutation{ 224 CommitNow: true, 225 SetNquads: []byte(fmt.Sprintf(`_:a <%s> "string" .`, predicateToWrite)), 226 }) 227 228 if shouldFail { 229 require.Error(t, err, "the mutation should have failed") 230 } else { 231 require.NoError(t, err, "the mutation should have succeeded") 232 } 233 } 234 235 func alterPredicateWithUserAccount(t *testing.T, dg *dgo.Dgraph, shouldFail bool) { 236 ctx := context.Background() 237 err := dg.Alter(ctx, &api.Operation{ 238 Schema: fmt.Sprintf(`%s: int .`, predicateToAlter), 239 }) 240 if shouldFail { 241 require.Error(t, err, "the alter should have failed") 242 } else { 243 require.NoError(t, err, "the alter should have succeeded") 244 } 245 } 246 247 func createAccountAndData(t *testing.T, dg *dgo.Dgraph) { 248 // use the groot account to clean the database 249 ctx := context.Background() 250 if err := dg.Login(ctx, x.GrootId, "password"); err != nil { 251 t.Fatalf("unable to login using the groot account:%v", err) 252 } 253 op := api.Operation{ 254 DropAll: true, 255 } 256 if err := dg.Alter(ctx, &op); err != nil { 257 t.Fatalf("Unable to cleanup db:%v", err) 258 } 259 require.NoError(t, dg.Alter(ctx, &api.Operation{ 260 Schema: fmt.Sprintf(`%s: string @index(exact) .`, predicateToRead), 261 })) 262 // wait for 6 seconds to ensure the new acl have reached all acl caches 263 glog.Infof("Sleeping for 6 seconds for acl caches to be refreshed") 264 time.Sleep(6 * time.Second) 265 266 // create some data, e.g. user with name alice 267 resetUser(t) 268 269 txn := dg.NewTxn() 270 _, err := txn.Mutate(ctx, &api.Mutation{ 271 SetNquads: []byte(fmt.Sprintf("_:a <%s> \"SF\" .", predicateToRead)), 272 }) 273 require.NoError(t, err) 274 require.NoError(t, txn.Commit(ctx)) 275 } 276 277 func createGroupAndAcls(t *testing.T, group string, addUserToGroup bool) { 278 // create a new group 279 createGroupCmd := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 280 "acl", "add", 281 "-a", dgraphEndpoint, 282 "-g", group, "-x", "password") 283 if errOutput, err := createGroupCmd.CombinedOutput(); err != nil { 284 t.Fatalf("Unable to create group: %v", string(errOutput)) 285 } 286 287 // add the user to the group 288 if addUserToGroup { 289 addUserToGroupCmd := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 290 "acl", "mod", 291 "-a", dgraphEndpoint, 292 "-u", userid, "--group_list", group, "-x", "password") 293 if errOutput, err := addUserToGroupCmd.CombinedOutput(); err != nil { 294 t.Fatalf("Unable to add user %s to group %s:%v", userid, group, string(errOutput)) 295 } 296 } 297 298 // add READ permission on the predicateToRead to the group 299 addReadPermCmd1 := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 300 "acl", "mod", 301 "-a", dgraphEndpoint, 302 "-g", group, "-p", predicateToRead, "-m", strconv.Itoa(int(Read.Code)), "-x", 303 "password") 304 if errOutput, err := addReadPermCmd1.CombinedOutput(); err != nil { 305 t.Fatalf("Unable to add READ permission on %s to group %s: %v", 306 predicateToRead, group, string(errOutput)) 307 } 308 309 // also add read permission to the attribute queryAttr, which is used inside the query block 310 addReadPermCmd2 := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 311 "acl", "mod", 312 "-a", dgraphEndpoint, 313 "-g", group, "-p", queryAttr, "-m", strconv.Itoa(int(Read.Code)), "-x", 314 "password") 315 if errOutput, err := addReadPermCmd2.CombinedOutput(); err != nil { 316 t.Fatalf("Unable to add READ permission on %s to group %s: %v", queryAttr, group, 317 string(errOutput)) 318 } 319 320 // add WRITE permission on the predicateToWrite 321 addWritePermCmd := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 322 "acl", "mod", 323 "-a", dgraphEndpoint, 324 "-g", group, "-p", predicateToWrite, "-m", strconv.Itoa(int(Write.Code)), "-x", 325 "password") 326 if errOutput, err := addWritePermCmd.CombinedOutput(); err != nil { 327 t.Fatalf("Unable to add permission on %s to group %s: %v", predicateToWrite, group, 328 string(errOutput)) 329 } 330 331 // add MODIFY permission on the predicateToAlter 332 addModifyPermCmd := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 333 "acl", "mod", 334 "-a", dgraphEndpoint, 335 "-g", group, "-p", predicateToAlter, "-m", strconv.Itoa(int(Modify.Code)), "-x", 336 "password") 337 if errOutput, err := addModifyPermCmd.CombinedOutput(); err != nil { 338 t.Fatalf("Unable to add permission on %s to group %s: %v", predicateToAlter, group, 339 string(errOutput)) 340 } 341 } 342 343 func TestPredicateRegex(t *testing.T) { 344 if testing.Short() { 345 t.Skip("skipping because -short=true") 346 } 347 348 glog.Infof("testing with port 9180") 349 dg := testutil.DgraphClientWithGroot(testutil.SockAddr) 350 createAccountAndData(t, dg) 351 ctx := context.Background() 352 err := dg.Login(ctx, userid, userpassword) 353 require.NoError(t, err, "Logging in with the current password should have succeeded") 354 355 // the operations should be allowed when no rule is defined (the fail open approach) 356 queryPredicateWithUserAccount(t, dg, false) 357 mutatePredicateWithUserAccount(t, dg, false) 358 alterPredicateWithUserAccount(t, dg, false) 359 createGroupAndAcls(t, unusedGroup, false) 360 361 // wait for 6 seconds to ensure the new acl have reached all acl caches 362 glog.Infof("Sleeping for 6 seconds for acl caches to be refreshed") 363 time.Sleep(6 * time.Second) 364 // the operations should all fail when there is a rule defined, but the current user is not 365 // allowed 366 queryPredicateWithUserAccount(t, dg, true) 367 mutatePredicateWithUserAccount(t, dg, true) 368 alterPredicateWithUserAccount(t, dg, true) 369 370 // create a new group 371 createGroupCmd := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 372 "acl", "add", 373 "-a", dgraphEndpoint, 374 "-g", devGroup, "-x", "password") 375 if errOutput, err := createGroupCmd.CombinedOutput(); err != nil { 376 t.Fatalf("Unable to create group:%v", string(errOutput)) 377 } 378 379 // add the user to the group 380 addUserToGroupCmd := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 381 "acl", "mod", 382 "-a", dgraphEndpoint, 383 "-u", userid, "--group_list", devGroup, "-x", "password") 384 if errOutput, err := addUserToGroupCmd.CombinedOutput(); err != nil { 385 t.Fatalf("Unable to add user %s to group %s:%v", userid, devGroup, string(errOutput)) 386 } 387 388 addReadToNameCmd := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 389 "acl", "mod", 390 "-a", dgraphEndpoint, 391 "-g", devGroup, "--pred", "name", "-m", strconv.Itoa(int(Read.Code)|int(Write.Code)), 392 "-x", 393 "password") 394 if errOutput, err := addReadToNameCmd.CombinedOutput(); err != nil { 395 t.Fatalf("Unable to add READ permission on %s to group %s:%v", 396 "name", devGroup, string(errOutput)) 397 } 398 399 // add READ+WRITE permission on the regex ^predicate_to(.*)$ pred filter to the group 400 predRegex := "^predicate_to(.*)$" 401 addReadWriteToRegexPermCmd := exec.Command(os.ExpandEnv("$GOPATH/bin/dgraph"), 402 "acl", "mod", 403 "-a", dgraphEndpoint, 404 "-g", devGroup, "-P", predRegex, "-m", 405 strconv.Itoa(int(Read.Code)|int(Write.Code)), "-x", "password") 406 if errOutput, err := addReadWriteToRegexPermCmd.CombinedOutput(); err != nil { 407 t.Fatalf("Unable to add READ+WRITE permission on %s to group %s:%v", 408 predRegex, devGroup, string(errOutput)) 409 } 410 411 glog.Infof("Sleeping for 6 seconds for acl caches to be refreshed") 412 time.Sleep(6 * time.Second) 413 queryPredicateWithUserAccount(t, dg, false) 414 mutatePredicateWithUserAccount(t, dg, false) 415 // the alter operation should still fail since the regex pred does not have the Modify 416 // permission 417 alterPredicateWithUserAccount(t, dg, true) 418 } 419 420 func TestAccessWithoutLoggingIn(t *testing.T) { 421 dg := testutil.DgraphClientWithGroot(testutil.SockAddr) 422 423 createAccountAndData(t, dg) 424 // without logging in, 425 // the anonymous user should be evaluated as if the user does not belong to any group, 426 // and access should be granted if there is no ACL rule defined for a predicate (fail open) 427 queryPredicateWithUserAccount(t, dg, false) 428 mutatePredicateWithUserAccount(t, dg, false) 429 alterPredicateWithUserAccount(t, dg, false) 430 }