github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/ee/acl/acl.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 "encoding/json" 18 "fmt" 19 "regexp" 20 "strings" 21 "time" 22 23 "github.com/dgraph-io/dgo" 24 "github.com/dgraph-io/dgo/protos/api" 25 "github.com/dgraph-io/dgraph/x" 26 "github.com/golang/glog" 27 "github.com/pkg/errors" 28 "github.com/spf13/viper" 29 ) 30 31 func getUserAndGroup(conf *viper.Viper) (userId string, groupId string, err error) { 32 userId = conf.GetString("user") 33 groupId = conf.GetString("group") 34 if (len(userId) == 0 && len(groupId) == 0) || 35 (len(userId) != 0 && len(groupId) != 0) { 36 return "", "", errors.Errorf("one of the --user or --group must be specified, but not both") 37 } 38 return userId, groupId, nil 39 } 40 41 func checkForbiddenOpts(conf *viper.Viper, forbiddenOpts []string) error { 42 for _, opt := range forbiddenOpts { 43 var isSet bool 44 switch conf.Get(opt).(type) { 45 case string: 46 if opt == "group_list" { 47 // handle group_list specially since the default value is not an empty string 48 isSet = conf.GetString(opt) != defaultGroupList 49 } else { 50 isSet = len(conf.GetString(opt)) > 0 51 } 52 case int: 53 isSet = conf.GetInt(opt) > 0 54 case bool: 55 isSet = conf.GetBool(opt) 56 default: 57 return errors.Errorf("unexpected option type for %s", opt) 58 } 59 if isSet { 60 return errors.Errorf("the option --%s should not be set", opt) 61 } 62 } 63 64 return nil 65 } 66 67 func add(conf *viper.Viper) error { 68 userId, groupId, err := getUserAndGroup(conf) 69 if err != nil { 70 return err 71 } 72 password := conf.GetString("password") 73 if len(userId) != 0 { 74 return userAdd(conf, userId, password) 75 } 76 77 // if we are adding a group, then the password should not have been set 78 if err := checkForbiddenOpts(conf, []string{"password"}); err != nil { 79 return err 80 } 81 return groupAdd(conf, groupId) 82 } 83 84 func userAdd(conf *viper.Viper, userid string, password string) error { 85 dc, cancel, err := getClientWithAdminCtx(conf) 86 if err != nil { 87 return errors.Wrapf(err, "unable to get admin context") 88 } 89 defer cancel() 90 91 if len(password) == 0 { 92 var err error 93 password, err = x.AskUserPassword(userid, "New", 2) 94 if err != nil { 95 return err 96 } 97 } 98 99 ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) 100 defer ctxCancel() 101 txn := dc.NewTxn() 102 defer func() { 103 if err := txn.Discard(ctx); err != nil { 104 glog.Errorf("Unable to discard transaction:%v", err) 105 } 106 }() 107 108 user, err := queryUser(ctx, txn, userid) 109 if err != nil { 110 return errors.Wrapf(err, "while querying user") 111 } 112 if user != nil { 113 return errors.Errorf("unable to create user because of conflict: %v", userid) 114 } 115 116 createUserNQuads := CreateUserNQuads(userid, password) 117 118 mu := &api.Mutation{ 119 CommitNow: true, 120 Set: createUserNQuads, 121 } 122 123 if _, err := txn.Mutate(ctx, mu); err != nil { 124 return errors.Wrapf(err, "unable to create user") 125 } 126 127 fmt.Printf("Created new user with id %v\n", userid) 128 return nil 129 } 130 131 func groupAdd(conf *viper.Viper, groupId string) error { 132 dc, cancel, err := getClientWithAdminCtx(conf) 133 if err != nil { 134 return errors.Wrapf(err, "unable to get admin context") 135 } 136 defer cancel() 137 138 ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) 139 defer ctxCancel() 140 txn := dc.NewTxn() 141 defer func() { 142 if err := txn.Discard(ctx); err != nil { 143 fmt.Printf("Unable to discard transaction: %v\n", err) 144 } 145 }() 146 147 group, err := queryGroup(ctx, txn, groupId) 148 if err != nil { 149 return errors.Wrapf(err, "while querying group") 150 } 151 if group != nil { 152 return errors.Errorf("group %q already exists", groupId) 153 } 154 155 createGroupNQuads := []*api.NQuad{ 156 { 157 Subject: "_:newgroup", 158 Predicate: "dgraph.xid", 159 ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: groupId}}, 160 }, 161 { 162 Subject: "_:newgroup", 163 Predicate: "dgraph.type", 164 ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: "Group"}}, 165 }, 166 } 167 168 mu := &api.Mutation{ 169 CommitNow: true, 170 Set: createGroupNQuads, 171 } 172 if _, err = txn.Mutate(ctx, mu); err != nil { 173 return errors.Wrapf(err, "unable to create group") 174 } 175 176 fmt.Printf("Created new group with id %v\n", groupId) 177 return nil 178 } 179 180 func del(conf *viper.Viper) error { 181 userId, groupId, err := getUserAndGroup(conf) 182 if err != nil { 183 return err 184 } 185 if len(userId) != 0 { 186 return userOrGroupDel(conf, userId, 187 func(ctx context.Context, txn *dgo.Txn, userId string) (AclEntity, error) { 188 user, err := queryUser(ctx, txn, userId) 189 return user, err 190 }) 191 } 192 return userOrGroupDel(conf, groupId, 193 func(ctx context.Context, txn *dgo.Txn, groupId string) (AclEntity, error) { 194 group, err := queryGroup(ctx, txn, groupId) 195 return group, err 196 }) 197 } 198 199 // AclEntity is an interface that must be met by all the types of entities (i.e users, groups) 200 // in the ACL system. 201 type AclEntity interface { 202 // GetUid returns the UID of the entity. 203 // The implementation of GetUid must check the case that the entity is nil 204 // and return an empty string accordingly. 205 GetUid() string 206 } 207 208 func userOrGroupDel(conf *viper.Viper, userOrGroupId string, 209 queryFn func(context.Context, *dgo.Txn, string) (AclEntity, error)) error { 210 dc, cancel, err := getClientWithAdminCtx(conf) 211 if err != nil { 212 return errors.Wrapf(err, "unable to get admin context") 213 } 214 defer cancel() 215 216 ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) 217 defer ctxCancel() 218 txn := dc.NewTxn() 219 defer func() { 220 if err := txn.Discard(ctx); err != nil { 221 glog.Errorf("Unable to discard transaction:%v", err) 222 } 223 }() 224 225 entity, err := queryFn(ctx, txn, userOrGroupId) 226 if err != nil { 227 return err 228 } 229 if len(entity.GetUid()) == 0 { 230 return errors.Errorf("unable to delete %q since it does not exist", 231 userOrGroupId) 232 } 233 234 deleteNQuads := []*api.NQuad{ 235 { 236 Subject: entity.GetUid(), 237 Predicate: x.Star, 238 ObjectValue: &api.Value{Val: &api.Value_DefaultVal{DefaultVal: x.Star}}, 239 }} 240 241 mu := &api.Mutation{ 242 CommitNow: true, 243 Del: deleteNQuads, 244 } 245 246 if _, err = txn.Mutate(ctx, mu); err != nil { 247 return errors.Wrapf(err, "unable to delete %q", userOrGroupId) 248 } 249 250 fmt.Printf("Successfully deleted %q\n", userOrGroupId) 251 return nil 252 } 253 254 func mod(conf *viper.Viper) error { 255 userId, _, err := getUserAndGroup(conf) 256 if err != nil { 257 return err 258 } 259 260 if len(userId) != 0 { 261 // when modifying the user, some group options are forbidden 262 if err := checkForbiddenOpts(conf, []string{"pred", "pred_regex", "perm"}); err != nil { 263 return err 264 } 265 266 newPassword := conf.GetBool("new_password") 267 groupList := conf.GetString("group_list") 268 if (newPassword && groupList != defaultGroupList) || 269 (!newPassword && groupList == defaultGroupList) { 270 return errors.Errorf( 271 "one of --new_password or --group_list must be provided, but not both") 272 } 273 274 if newPassword { 275 return changePassword(conf, userId) 276 } 277 278 return userMod(conf, userId, groupList) 279 } 280 281 // when modifying the group, some user options are forbidden 282 if err := checkForbiddenOpts(conf, []string{"group_list", "new_password"}); err != nil { 283 return err 284 } 285 return chMod(conf) 286 } 287 288 // changePassword changes a user's password 289 func changePassword(conf *viper.Viper, userId string) error { 290 // 1. get the dgo client with appropriate access JWT 291 dc, cancel, err := getClientWithAdminCtx(conf) 292 if err != nil { 293 return errors.Wrapf(err, "unable to get dgo client") 294 } 295 defer cancel() 296 297 // 2. get the new password 298 newPassword, err := x.AskUserPassword(userId, "New", 2) 299 if err != nil { 300 return err 301 } 302 303 ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) 304 defer ctxCancel() 305 txn := dc.NewTxn() 306 defer func() { 307 if err := txn.Discard(ctx); err != nil { 308 glog.Errorf("Unable to discard transaction:%v", err) 309 } 310 }() 311 312 // 3. query the user's current uid 313 user, err := queryUser(ctx, txn, userId) 314 if err != nil { 315 return errors.Wrapf(err, "while querying user") 316 } 317 if user == nil { 318 return errors.Errorf("user %q does not exist", userId) 319 } 320 321 // 4. mutate the user's password 322 chPdNQuads := []*api.NQuad{ 323 { 324 Subject: user.Uid, 325 Predicate: "dgraph.password", 326 ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: newPassword}}, 327 }} 328 mu := &api.Mutation{ 329 CommitNow: true, 330 Set: chPdNQuads, 331 } 332 if _, err := txn.Mutate(ctx, mu); err != nil { 333 return errors.Wrapf(err, "unable to change password for user %v", userId) 334 } 335 fmt.Printf("Successfully changed password for %v\n", userId) 336 return nil 337 } 338 339 func userMod(conf *viper.Viper, userId string, groups string) error { 340 dc, cancel, err := getClientWithAdminCtx(conf) 341 if err != nil { 342 return errors.Wrapf(err, "unable to get admin context") 343 } 344 defer cancel() 345 346 ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) 347 defer ctxCancel() 348 txn := dc.NewTxn() 349 defer func() { 350 if err := txn.Discard(ctx); err != nil { 351 fmt.Printf("Unable to discard transaction: %v\n", err) 352 } 353 }() 354 355 user, err := queryUser(ctx, txn, userId) 356 if err != nil { 357 return errors.Wrapf(err, "while querying user") 358 } 359 if user == nil { 360 return errors.Errorf("user %q does not exist", userId) 361 } 362 363 targetGroupsMap := make(map[string]struct{}) 364 if len(groups) > 0 { 365 for _, g := range strings.Split(groups, ",") { 366 targetGroupsMap[g] = struct{}{} 367 } 368 } 369 370 existingGroupsMap := make(map[string]struct{}) 371 for _, g := range user.Groups { 372 existingGroupsMap[g.GroupID] = struct{}{} 373 } 374 newGroups, groupsToBeDeleted := x.Diff(targetGroupsMap, existingGroupsMap) 375 376 mu := &api.Mutation{ 377 CommitNow: true, 378 Set: []*api.NQuad{}, 379 Del: []*api.NQuad{}, 380 } 381 382 for _, g := range newGroups { 383 fmt.Printf("Adding user %v to group %v\n", userId, g) 384 nquad, err := getUserModNQuad(ctx, txn, user.Uid, g) 385 if err != nil { 386 return err 387 } 388 mu.Set = append(mu.Set, nquad) 389 } 390 391 for _, g := range groupsToBeDeleted { 392 fmt.Printf("Deleting user %v from group %v\n", userId, g) 393 nquad, err := getUserModNQuad(ctx, txn, user.Uid, g) 394 if err != nil { 395 return err 396 } 397 mu.Del = append(mu.Del, nquad) 398 } 399 if len(mu.Del) == 0 && len(mu.Set) == 0 { 400 fmt.Printf("Nothing needs to be changed for the groups of user: %v\n", userId) 401 return nil 402 } 403 404 if _, err := txn.Mutate(ctx, mu); err != nil { 405 return errors.Wrapf(err, "while mutating the group") 406 } 407 fmt.Printf("Successfully modified groups for user %v.\n", userId) 408 fmt.Println("The latest info is:") 409 return queryAndPrintUser(ctx, dc.NewReadOnlyTxn(), userId) 410 } 411 412 func chMod(conf *viper.Viper) error { 413 groupId := conf.GetString("group") 414 predicate := conf.GetString("pred") 415 predRegex := conf.GetString("pred_regex") 416 perm := conf.GetInt("perm") 417 switch { 418 case len(groupId) == 0: 419 return errors.Errorf("the groupid must not be empty") 420 case len(predicate) > 0 && len(predRegex) > 0: 421 return errors.Errorf("one of --pred or --pred_regex must be specified, but not both") 422 case len(predicate) == 0 && len(predRegex) == 0: 423 return errors.Errorf("one of --pred or --pred_regex must be specified, but not both") 424 case perm > 7: 425 return errors.Errorf("the perm value must be less than or equal to 7, "+ 426 "the provided value is %d", perm) 427 case len(predRegex) > 0: 428 // make sure the predRegex can be compiled as a regex 429 if _, err := regexp.Compile(predRegex); err != nil { 430 return errors.Wrapf(err, "unable to compile %v as a regular expression", 431 predRegex) 432 } 433 } 434 435 dc, cancel, err := getClientWithAdminCtx(conf) 436 if err != nil { 437 return errors.Wrapf(err, "unable to get admin context") 438 } 439 defer cancel() 440 441 ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) 442 defer ctxCancel() 443 txn := dc.NewTxn() 444 defer func() { 445 if err := txn.Discard(ctx); err != nil { 446 fmt.Printf("Unable to discard transaction: %v\n", err) 447 } 448 }() 449 450 group, err := queryGroup(ctx, txn, groupId, "dgraph.group.acl") 451 if err != nil { 452 return errors.Wrapf(err, "while querying group") 453 } 454 if group == nil || len(group.Uid) == 0 { 455 return errors.Errorf("unable to change permission for group because it does not exist: %v", 456 groupId) 457 } 458 459 var currentAcls []Acl 460 if len(group.Acls) != 0 { 461 if err := json.Unmarshal([]byte(group.Acls), ¤tAcls); err != nil { 462 return errors.Wrapf(err, "unable to unmarshal the acls associated with the group %v", 463 groupId) 464 } 465 } 466 467 var newAcl Acl 468 if len(predicate) > 0 { 469 newAcl = Acl{ 470 Predicate: predicate, 471 Perm: int32(perm), 472 } 473 } else { 474 newAcl = Acl{ 475 Regex: predRegex, 476 Perm: int32(perm), 477 } 478 } 479 newAcls, updated := updateAcl(currentAcls, newAcl) 480 if !updated { 481 fmt.Printf("Nothing needs to be changed for the permission of group: %v\n", groupId) 482 return nil 483 } 484 485 newAclBytes, err := json.Marshal(newAcls) 486 if err != nil { 487 return errors.Wrapf(err, "unable to marshal the updated acls") 488 } 489 490 chModNQuads := &api.NQuad{ 491 Subject: group.Uid, 492 Predicate: "dgraph.group.acl", 493 ObjectValue: &api.Value{Val: &api.Value_BytesVal{BytesVal: newAclBytes}}, 494 } 495 mu := &api.Mutation{ 496 CommitNow: true, 497 Set: []*api.NQuad{chModNQuads}, 498 } 499 500 if _, err = txn.Mutate(ctx, mu); err != nil { 501 return errors.Wrapf(err, "unable to change mutations for the group %v on predicate %v", 502 groupId, predicate) 503 } 504 fmt.Printf("Successfully changed permission for group %v on predicate %v to %v\n", 505 groupId, predicate, perm) 506 fmt.Println("The latest info is:") 507 return queryAndPrintGroup(ctx, dc.NewReadOnlyTxn(), groupId) 508 } 509 510 func queryUser(ctx context.Context, txn *dgo.Txn, userid string) (user *User, err error) { 511 query := ` 512 query search($userid: string){ 513 user(func: eq(dgraph.xid, $userid)) @filter(type(User)) { 514 uid 515 dgraph.xid 516 dgraph.user.group { 517 uid 518 dgraph.xid 519 } 520 } 521 }` 522 523 queryVars := make(map[string]string) 524 queryVars["$userid"] = userid 525 526 queryResp, err := txn.QueryWithVars(ctx, query, queryVars) 527 if err != nil { 528 return nil, errors.Wrapf(err, "hile query user with id %s", userid) 529 } 530 user, err = UnmarshalUser(queryResp, "user") 531 if err != nil { 532 return nil, err 533 } 534 return user, nil 535 } 536 537 func getUserModNQuad(ctx context.Context, txn *dgo.Txn, userId string, 538 groupId string) (*api.NQuad, error) { 539 group, err := queryGroup(ctx, txn, groupId) 540 if err != nil { 541 return nil, err 542 } 543 if group == nil { 544 return nil, errors.Errorf("group %q does not exist", groupId) 545 } 546 547 createUserGroupNQuads := &api.NQuad{ 548 Subject: userId, 549 Predicate: "dgraph.user.group", 550 ObjectId: group.Uid, 551 } 552 553 return createUserGroupNQuads, nil 554 } 555 556 func queryGroup(ctx context.Context, txn *dgo.Txn, groupid string, 557 fields ...string) (group *Group, err error) { 558 559 // write query header 560 query := fmt.Sprintf(`query search($groupid: string){ 561 group(func: eq(dgraph.xid, $groupid)) @filter(type(Group)) { 562 uid 563 %s }}`, strings.Join(fields, ", ")) 564 565 queryVars := map[string]string{ 566 "$groupid": groupid, 567 } 568 569 queryResp, err := txn.QueryWithVars(ctx, query, queryVars) 570 if err != nil { 571 fmt.Printf("Error while querying group with id %s: %v\n", groupid, err) 572 return nil, err 573 } 574 group, err = UnmarshalGroup(queryResp.GetJson(), "group") 575 if err != nil { 576 return nil, err 577 } 578 return group, nil 579 } 580 581 func isSameAcl(acl1 *Acl, acl2 *Acl) bool { 582 return (len(acl1.Predicate) > 0 && len(acl2.Predicate) > 0 && 583 acl1.Predicate == acl2.Predicate) || 584 (len(acl1.Regex) > 0 && len(acl2.Regex) > 0 && acl1.Regex == acl2.Regex) 585 } 586 587 // returns whether the existing acls slice is changed 588 func updateAcl(acls []Acl, newAcl Acl) ([]Acl, bool) { 589 for idx, aclEntry := range acls { 590 if isSameAcl(&aclEntry, &newAcl) { 591 if aclEntry.Perm == newAcl.Perm { 592 // new permission is the same as the current one, no update 593 return acls, false 594 } 595 if newAcl.Perm < 0 { 596 // remove the current aclEntry from the array 597 copy(acls[idx:], acls[idx+1:]) 598 return acls[:len(acls)-1], true 599 } 600 acls[idx].Perm = newAcl.Perm 601 return acls, true 602 } 603 } 604 605 // we do not find any existing aclEntry matching the newAcl predicate 606 return append(acls, newAcl), true 607 } 608 609 func queryAndPrintUser(ctx context.Context, txn *dgo.Txn, userId string) error { 610 user, err := queryUser(ctx, txn, userId) 611 if err != nil { 612 return err 613 } 614 if user == nil { 615 return errors.Errorf("The user %q does not exist.\n", userId) 616 } 617 618 fmt.Printf("User : %s\n", userId) 619 fmt.Printf("UID : %s\n", user.Uid) 620 for _, group := range user.Groups { 621 fmt.Printf("Group : %-5s\n", group.GroupID) 622 } 623 return nil 624 } 625 626 func queryAndPrintGroup(ctx context.Context, txn *dgo.Txn, groupId string) error { 627 group, err := queryGroup(ctx, txn, groupId, "dgraph.xid", "~dgraph.user.group{dgraph.xid}", 628 "dgraph.group.acl") 629 if err != nil { 630 return err 631 } 632 if group == nil { 633 return errors.Errorf("The group %q does not exist.\n", groupId) 634 } 635 fmt.Printf("Group: %s\n", groupId) 636 fmt.Printf("UID : %s\n", group.Uid) 637 fmt.Printf("ID : %s\n", group.GroupID) 638 639 var userNames []string 640 for _, user := range group.Users { 641 userNames = append(userNames, user.UserID) 642 } 643 fmt.Printf("Users: %s\n", strings.Join(userNames, " ")) 644 645 var acls []Acl 646 if len(group.Acls) != 0 { 647 if err := json.Unmarshal([]byte(group.Acls), &acls); err != nil { 648 return errors.Wrapf(err, "unable to unmarshal the acls associated with the group %v", 649 groupId) 650 } 651 652 for _, acl := range acls { 653 fmt.Printf("ACL : %v\n", acl) 654 } 655 } 656 return nil 657 } 658 659 func info(conf *viper.Viper) error { 660 userId, groupId, err := getUserAndGroup(conf) 661 if err != nil { 662 return err 663 } 664 665 dc, cancel, err := getClientWithAdminCtx(conf) 666 defer cancel() 667 if err != nil { 668 return errors.Wrapf(err, "unable to get admin context") 669 } 670 671 ctx, ctxCancel := context.WithTimeout(context.Background(), 10*time.Second) 672 defer ctxCancel() 673 txn := dc.NewTxn() 674 defer func() { 675 if err := txn.Discard(ctx); err != nil { 676 fmt.Printf("Unable to discard transaction: %v\n", err) 677 } 678 }() 679 680 if len(userId) != 0 { 681 return queryAndPrintUser(ctx, txn, userId) 682 } 683 684 return queryAndPrintGroup(ctx, txn, groupId) 685 }