From 87c50d408d9fbaf7b0f4318a27617b0ed7837019 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 29 Nov 2018 09:52:22 -0800 Subject: [PATCH 01/30] Merged with master --- dgraph/cmd/alpha/run.go | 19 +++ dgraph/cmd/live/run.go | 34 +---- dgraph/cmd/root.go | 3 +- edgraph/server.go | 5 + ee/acl/accessserver.go | 136 ++++++++++++++++++ ee/acl/acl_test.go | 109 +++++++++++++++ ee/acl/cmd/groups.go | 181 ++++++++++++++++++++++++ ee/acl/cmd/run.go | 180 ++++++++++++++++++++++++ ee/acl/cmd/users.go | 283 ++++++++++++++++++++++++++++++++++++++ ee/acl/jwt.go | 123 +++++++++++++++++ query/mutation.go | 3 + systest/mutations_test.go | 4 +- worker/groups.go | 37 ++++- x/x.go | 40 ++++++ 14 files changed, 1116 insertions(+), 41 deletions(-) create mode 100644 ee/acl/accessserver.go create mode 100644 ee/acl/acl_test.go create mode 100644 ee/acl/cmd/groups.go create mode 100644 ee/acl/cmd/run.go create mode 100644 ee/acl/cmd/users.go create mode 100644 ee/acl/jwt.go diff --git a/dgraph/cmd/alpha/run.go b/dgraph/cmd/alpha/run.go index 872ec83140e..2c38d61d891 100644 --- a/dgraph/cmd/alpha/run.go +++ b/dgraph/cmd/alpha/run.go @@ -21,6 +21,7 @@ import ( "crypto/tls" "errors" "fmt" + "io/ioutil" "log" "net" "net/http" @@ -32,6 +33,8 @@ import ( "syscall" "time" + "github.com/dgraph-io/dgraph/ee/acl" + "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/edgraph" "github.com/dgraph-io/dgraph/posting" @@ -120,6 +123,8 @@ they form a Raft group and provide synchronous replication. "If set, all Alter requests to Dgraph would need to have this token."+ " The token can be passed as follows: For HTTP requests, in X-Dgraph-AuthToken header."+ " For Grpc, in auth-token key in the context.") + flag.String("hmac_secret_file", "", "The file storing the HMAC secret"+ + " that is used for signing the JWT") flag.Float64P("lru_mb", "l", -1, "Estimated memory the LRU cache can take. "+ "Actual usage by the process would be more than specified here.") @@ -274,6 +279,7 @@ func serveGRPC(l net.Listener, tlsCfg *tls.Config, wg *sync.WaitGroup) { s := grpc.NewServer(opt...) api.RegisterDgraphServer(s, &edgraph.Server{}) + api.RegisterDgraphAccessServer(s, &acl.AccessServer{}) hapi.RegisterHealthServer(s, health.NewServer()) err := s.Serve(l) glog.Errorf("GRPC listener canceled: %v\n", err) @@ -392,6 +398,19 @@ func run() { AllottedMemory: Alpha.Conf.GetFloat64("lru_mb"), }) + secretFile := Alpha.Conf.GetString("hmac_secret_file") + if secretFile != "" { + hmacSecret, err := ioutil.ReadFile(secretFile) + if err != nil { + glog.Fatalf("unable to read hmac secret from file: %v", secretFile) + } + + acl.SetAccessConfiguration(acl.AccessOptions{ + HmacSecret: hmacSecret, + }) + glog.Info("HMAC secret loaded successfully.") + } + ips, err := parseIPsFromString(Alpha.Conf.GetString("whitelist")) x.Check(err) worker.Config = worker.Options{ diff --git a/dgraph/cmd/live/run.go b/dgraph/cmd/live/run.go index 8db0f5c0733..8b974e81a87 100644 --- a/dgraph/cmd/live/run.go +++ b/dgraph/cmd/live/run.go @@ -34,8 +34,6 @@ import ( "strings" "time" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "github.com/dgraph-io/badger" @@ -239,34 +237,6 @@ func (l *loader) processFile(ctx context.Context, file string) error { return nil } -func setupConnection(host string, insecure bool) (*grpc.ClientConn, error) { - if insecure { - return grpc.Dial(host, - grpc.WithDefaultCallOptions( - grpc.MaxCallRecvMsgSize(x.GrpcMaxSize), - grpc.MaxCallSendMsgSize(x.GrpcMaxSize)), - grpc.WithInsecure(), - grpc.WithBlock(), - grpc.WithTimeout(10*time.Second)) - } - - tlsConf.ConfigType = x.TLSClientConfig - tlsConf.Cert = filepath.Join(tlsConf.CertDir, tlsLiveCert) - tlsConf.Key = filepath.Join(tlsConf.CertDir, tlsLiveKey) - tlsCfg, _, err := x.GenerateTLSConfig(tlsConf) - if err != nil { - return nil, err - } - - return grpc.Dial(host, - grpc.WithDefaultCallOptions( - grpc.MaxCallRecvMsgSize(x.GrpcMaxSize), - grpc.MaxCallSendMsgSize(x.GrpcMaxSize)), - grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)), - grpc.WithBlock(), - grpc.WithTimeout(10*time.Second)) -} - func fileList(files string) []string { if len(files) == 0 { return []string{} @@ -285,7 +255,7 @@ func setup(opts batchMutationOptions, dc *dgo.Dgraph) *loader { kv, err := badger.Open(o) x.Checkf(err, "Error while creating badger KV posting store") - connzero, err := setupConnection(opt.zero, true) + connzero, err := x.SetupConnection(opt.zero, true, &tlsConf, tlsLiveCert, tlsLiveKey) x.Checkf(err, "Unable to connect to zero, Is it running at %s?", opt.zero) alloc := xidmap.New( @@ -345,7 +315,7 @@ func run() error { ds := strings.Split(opt.dgraph, ",") var clients []api.DgraphClient for _, d := range ds { - conn, err := setupConnection(d, !tlsConf.CertRequired) + conn, err := x.SetupConnection(d, !tlsConf.CertRequired, &tlsConf, tlsLiveCert, tlsLiveKey) x.Checkf(err, "While trying to setup connection to Dgraph alpha.") defer conn.Close() diff --git a/dgraph/cmd/root.go b/dgraph/cmd/root.go index 1a288a0592b..8ffac4ef944 100644 --- a/dgraph/cmd/root.go +++ b/dgraph/cmd/root.go @@ -19,6 +19,7 @@ package cmd import ( goflag "flag" "fmt" + "github.com/dgraph-io/dgraph/ee/acl/cmd" "os" "github.com/dgraph-io/dgraph/dgraph/cmd/alpha" @@ -86,7 +87,7 @@ func init() { var subcommands = []*x.SubCommand{ &bulk.Bulk, &cert.Cert, &conv.Conv, &live.Live, &alpha.Alpha, &zero.Zero, - &version.Version, &debug.Debug, + &version.Version, &debug.Debug, &acl.Acl, } for _, sc := range subcommands { RootCmd.AddCommand(sc.Cmd) diff --git a/edgraph/server.go b/edgraph/server.go index 7562b2a794d..4e1d9992376 100644 --- a/edgraph/server.go +++ b/edgraph/server.go @@ -598,6 +598,11 @@ func parseNQuads(b []byte) ([]*api.NQuad, error) { return nqs, nil } +// parseMutationObject tries to consolidate fields of the api.Mutation into the +// corresponding field of the returned gql.Mutation. For example, the 3 fields, +// api.Mutation#SetJson, api.Mutation#SetNquads and api.Mutation#Set are consolidated into the +// gql.Mutation.Set field. Similarly the 3 fields api.Mutation#DeleteJson, api.Mutation#DelNquads +// and api.Mutation#Del are merged into the gql.Mutation#Del field func parseMutationObject(mu *api.Mutation) (*gql.Mutation, error) { res := &gql.Mutation{} if len(mu.SetJson) > 0 { diff --git a/ee/acl/accessserver.go b/ee/acl/accessserver.go new file mode 100644 index 00000000000..5dc8f236453 --- /dev/null +++ b/ee/acl/accessserver.go @@ -0,0 +1,136 @@ +package acl + +import ( + "context" + "fmt" + "time" + + "github.com/dgraph-io/dgo/protos/api" + "github.com/dgraph-io/dgraph/edgraph" + "github.com/dgraph-io/dgraph/ee/acl/cmd" + "github.com/dgraph-io/dgraph/x" + "github.com/golang/glog" +) + +// Server implements protos.DgraphAccessServer +type AccessServer struct{} + +type AccessOptions struct { + HmacSecret []byte +} + +var accessConfig AccessOptions + +func SetAccessConfiguration(newConfig AccessOptions) { + accessConfig = newConfig +} + +func (accessServer *AccessServer) LogIn(ctx context.Context, + request *api.LogInRequest) (*api.LogInResponse, error) { + err := validateLoginRequest(request) + if err != nil { + return nil, err + } + + resp := &api.LogInResponse{ + Code: api.AclResponseCode_UNAUTHENTICATED, + Context: &api.TxnContext{}, + } + + dbUser, err := queryDBUser(ctx, request.Userid) + if err != nil { + glog.Infof("Unable to login user with user id: %v", request.Userid) + return nil, err + } + if dbUser == nil { + errMsg := fmt.Sprintf("user not found for user id %v", request.Userid) + glog.Errorf(errMsg) + return nil, fmt.Errorf(errMsg) + } + + if len(dbUser.Password) == 0 { + errMsg := "Unable to authenticate since the user's password is empty" + glog.Errorf(errMsg) + return nil, fmt.Errorf(errMsg) + } + + if dbUser.Password != request.Password { + glog.Infof("Password mismatch for user: %v", request.Userid) + resp.Code = api.AclResponseCode_UNAUTHENTICATED + return resp, nil + } + + jwt := &Jwt{ + Header: StdJwtHeader, + Payload: JwtPayload{ + Userid: request.Userid, + Groups: toJwtGroups(dbUser.Groups), + Exp: time.Now().AddDate(0, 0, 30).Unix(), // set the jwt valid for 30 days + }, + } + + resp.Context.Jwt, err = jwt.EncodeToString(accessConfig.HmacSecret) + if err != nil { + glog.Errorf("Unable to encode jwt to string: %v", err) + return nil, err + } + resp.Code = api.AclResponseCode_OK + return resp, nil +} + +func validateLoginRequest(request *api.LogInRequest) error { + if request == nil { + return fmt.Errorf("the request should not be nil") + } + if len(request.Userid) == 0 { + return fmt.Errorf("the userid should not be empty") + } + if len(request.Password) == 0 { + return fmt.Errorf("the password should not be empty") + } + return nil +} + +func queryDBUser(ctx context.Context, userid string) (dbUser *acl.DBUser, err error) { + queryUid := ` + query search($userid: string){ + user(func: eq(` + x.Acl_XId + `, $userid)) { + uid, + ` + x.Acl_Password + ` + ` + x.Acl_UserGroup + ` { + uid + dgraph.xid + } + } + }` + + queryVars := make(map[string]string) + queryVars["$userid"] = userid + queryRequest := api.Request{ + Query: queryUid, + Vars: queryVars, + } + + queryResp, err := (&edgraph.Server{}).Query(ctx, &queryRequest) + if err != nil { + glog.Errorf("Error while query user with id %s: %v", userid, err) + return nil, err + } + dbUser, err = acl.UnmarshallDBUser(queryResp, "user") + if err != nil { + return nil, err + } + return dbUser, nil +} + +func toJwtGroups(groups []acl.DBGroup) []JwtGroup { + jwtGroups := make([]JwtGroup, len(groups)) + + for _, g := range groups { + jwtGroups = append(jwtGroups, JwtGroup{ + Group: g.GroupID, + Wildcardacl: "", // TODO set it to the wild card acl returned from DB + }) + } + return jwtGroups +} diff --git a/ee/acl/acl_test.go b/ee/acl/acl_test.go new file mode 100644 index 00000000000..6711138ab26 --- /dev/null +++ b/ee/acl/acl_test.go @@ -0,0 +1,109 @@ +package acl + +import ( + "os/exec" + "testing" +) + +const ( + userid = "alice" + userpassword = "simplepassword" + dgraphEndpoint = "localhost:9180" +) + +func TestAcl(t *testing.T) { + t.Run("create user", CreateAndDeleteUsers) + t.Run("login", LogIn) +} + +func checkOutput(t *testing.T, cmd *exec.Cmd, shouldFail bool) string { + out, err := cmd.CombinedOutput() + if (!shouldFail && err != nil) || (shouldFail && err == nil) { + t.Errorf("Error output from command:%v", string(out)) + t.Fatal(err) + } + + return string(out) +} + +func CreateAndDeleteUsers(t *testing.T) { + createUserCmd1 := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", "lucas", + "-p", "haha") + createUserOutput1 := checkOutput(t, createUserCmd1, false) + t.Logf("Got output when creating user:%v", createUserOutput1) + + createUserCmd2 := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", "lucas", + "-p", "haha") + + // create the user again should fail + createUserOutput2 := checkOutput(t, createUserCmd2, true) + t.Logf("Got output when creating user:%v", createUserOutput2) + + // delete the user + deleteUserCmd := exec.Command("dgraph", "acl", "userdel", "-d", dgraphEndpoint, "-u", "lucas") + deleteUserOutput := checkOutput(t, deleteUserCmd, false) + t.Logf("Got output when deleting user:%v", deleteUserOutput) + + // now we should be able to create the user again + createUserCmd3 := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", "lucas", + "-p", "haha") + createUserOutput3 := checkOutput(t, createUserCmd3, false) + t.Logf("Got output when creating user:%v", createUserOutput3) +} + +func LogIn(t *testing.T) { + // delete and recreate the user to ensure a clean state + /* + deleteUserCmd := exec.Command("dgraph", "acl", "userdel", "-d", dgraphEndpoint, "-u", "lucas") + deleteUserOutput := checkOutput(t, deleteUserCmd, false) + createUserCmd := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", "lucas", + "-p", "haha") + createUserOutput := checkOutput(t, createUserCmd, false) + */ + + // now try to login with the wrong password + + //loginWithWrongPassword(t, ctx, adminClient) + //loginWithCorrectPassword(t, ctx, adminClient) +} + +/* +func loginWithCorrectPassword(t *testing.T, ctx context.Context, + adminClient api.DgraphAccessClient) { + loginRequest := api.LogInRequest{ + Userid: userid, + Password: userpassword, + } + response2, err := adminClient.LogIn(ctx, &loginRequest) + require.NoError(t, err) + if response2.Code != api.AclResponseCode_OK { + t.Errorf("Login with the correct password should result in the code %v", + api.AclResponseCode_OK) + } + jwt := acl.Jwt{} + jwt.DecodeString(response2.Context.Jwt, false, nil) + if jwt.Payload.Userid != userid { + t.Errorf("the jwt token should have the user id encoded") + } + jwtTime := time.Unix(jwt.Payload.Exp, 0) + jwtValidDays := jwtTime.Sub(time.Now()).Round(time.Hour).Hours() / 24 + if jwtValidDays != 30.0 { + t.Errorf("The jwt token should be valid for 30 days, received %v days", jwtValidDays) + } +} + +func loginWithWrongPassword(t *testing.T, ctx context.Context, + adminClient api.DgraphAccessClient) { + loginRequestWithWrongPassword := api.LogInRequest{ + Userid: userid, + Password: userpassword + "123", + } + + response, err := adminClient.LogIn(ctx, &loginRequestWithWrongPassword) + require.NoError(t, err) + if response.Code != api.AclResponseCode_UNAUTHENTICATED { + t.Errorf("Login with the wrong password should result in the code %v", api.AclResponseCode_UNAUTHENTICATED) + } +} + +*/ diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go new file mode 100644 index 00000000000..b1167402798 --- /dev/null +++ b/ee/acl/cmd/groups.go @@ -0,0 +1,181 @@ +package acl + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/dgraph-io/dgo" + "github.com/dgraph-io/dgo/protos/api" + "github.com/dgraph-io/dgraph/x" + "github.com/golang/glog" +) + +func groupAdd(dc *dgo.Dgraph) error { + groupId := GroupAdd.Conf.GetString("group") + if len(groupId) == 0 { + return fmt.Errorf("the group id should not be empty") + } + + ctx := context.Background() + txn := dc.NewTxn() + defer txn.Discard(ctx) + + dbGroup, err := queryDBGroup(txn, ctx, groupId) + if err != nil { + return err + } + + if dbGroup != nil { + return fmt.Errorf("The group with id %v already exists.", groupId) + } + + createGroupNQuads := []*api.NQuad{ + { + Subject: "_:" + x.NewEntityLabel, + Predicate: x.Acl_XId, + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: groupId}}, + }, + } + + mu := &api.Mutation{ + CommitNow: true, + Set: createGroupNQuads, + } + + _, err = txn.Mutate(ctx, mu) + if err != nil { + return fmt.Errorf("Unable to create user: %v", err) + } + + glog.Infof("Created new group with id %v", groupId) + return nil +} + +func groupDel(dc *dgo.Dgraph) error { + groupId := GroupDel.Conf.GetString("group") + if len(groupId) == 0 { + return fmt.Errorf("the group id should not be empty") + } + + ctx := context.Background() + txn := dc.NewTxn() + defer txn.Discard(ctx) + + dbGroup, err := queryDBGroup(txn, ctx, groupId) + if err != nil { + return err + } + + if dbGroup == nil || len(dbGroup.Uid) == 0 { + return fmt.Errorf("Unable to delete group because it does not exist: %v", groupId) + } + + deleteGroupNQuads := []*api.NQuad{ + { + Subject: dbGroup.Uid, + Predicate: x.Star, + ObjectValue: &api.Value{Val: &api.Value_DefaultVal{DefaultVal: x.Star}}, + }} + mu := &api.Mutation{ + CommitNow: true, + Del: deleteGroupNQuads, + } + + _, err = txn.Mutate(ctx, mu) + if err != nil { + return fmt.Errorf("Unable to delete group: %v", err) + } + + glog.Infof("Deleted user with id %v", groupId) + return nil +} + +func queryDBGroup(txn *dgo.Txn, ctx context.Context, groupid string) (dbGroup *DBGroup, err error) { + queryUid := ` + query search($groupid: string){ + group(func: eq(` + x.Acl_XId + `, $groupid)) { + uid, + ` + x.Acl_XId + ` + } + }` + + queryVars := make(map[string]string) + queryVars["$groupid"] = groupid + + queryResp, err := txn.QueryWithVars(ctx, queryUid, queryVars) + if err != nil { + glog.Errorf("Error while query group with id %s: %v", groupid, err) + return nil, err + } + dbGroup, err = UnmarshallDBGroup(queryResp, "group") + if err != nil { + return nil, err + } + return dbGroup, nil +} + +// Extract the first DBUser pointed by the userKey in the query response +func UnmarshallDBGroup(queryResp *api.Response, groupKey string) (dbGroup *DBGroup, err error) { + m := make(map[string][]DBGroup) + + err = json.Unmarshal(queryResp.GetJson(), &m) + if err != nil { + glog.Errorf("Unable to unmarshal the query group response") + return nil, err + } + groups := m[groupKey] + if len(groups) == 0 { + // the group does not exist + return nil, nil + } + + return &groups[0], nil +} + +// parse the response and check existing of the uid +type DBGroup struct { + Uid string `json:"uid"` + GroupID string `json:"dgraph.xid"` +} + +func chMod(dc *dgo.Dgraph) error { + groupId := ChMod.Conf.GetString("group") + predicate := ChMod.Conf.GetString("predicate") + acl := ChMod.Conf.GetInt64("acl") + if len(groupId) == 0 { + return fmt.Errorf("the groupid must not be empty") + } + if len(predicate) == 0 { + return fmt.Errorf("the predicate must not be empty") + } + + ctx := context.Background() + txn := dc.NewTxn() + defer txn.Discard(ctx) + + dbGroup, err := queryDBGroup(txn, ctx, groupId) + if err != nil { + return err + } + + if dbGroup == nil || len(dbGroup.Uid) == 0 { + return fmt.Errorf("Unable to change permission for group because it does not exist: %v", groupId) + } + chModNQuads := &api.NQuad{ + Subject: dbGroup.Uid, + Predicate: predicate, + ObjectValue: &api.Value{Val: &api.Value_IntVal{IntVal: acl}}, + } + mu := &api.Mutation{ + CommitNow: true, + Set: []*api.NQuad{chModNQuads}, + } + + _, err = txn.Mutate(ctx, mu) + if err != nil { + return fmt.Errorf("unable to change mutations for the group %v on predicate %v: %v", groupId, predicate, err) + } + glog.Infof("Successfully changed permission for group %v on predicate %v to %v", groupId, predicate, acl) + return nil +} diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go new file mode 100644 index 00000000000..4fc2e3a3b84 --- /dev/null +++ b/ee/acl/cmd/run.go @@ -0,0 +1,180 @@ +package acl + +import ( + "os" + "strings" + + "github.com/dgraph-io/dgo" + "github.com/dgraph-io/dgo/protos/api" + "github.com/dgraph-io/dgraph/x" + "github.com/golang/glog" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +type options struct { + dgraph string +} + +var opt options +var tlsConf x.TLSHelperConfig + +var Acl x.SubCommand +var UserAdd x.SubCommand +var UserDel x.SubCommand +var LogIn x.SubCommand + +var GroupAdd x.SubCommand +var GroupDel x.SubCommand + +var UserMod x.SubCommand +var ChMod x.SubCommand + +const ( + tlsAclCert = "client.acl.crt" + tlsAclKey = "client.acl.key" +) + +func init() { + Acl.Cmd = &cobra.Command{ + Use: "acl", + Short: "Run the Dgraph acl tool", + } + + flag := Acl.Cmd.PersistentFlags() + flag.StringP("dgraph", "d", "127.0.0.1:9080", "Dgraph gRPC server address") + + // TLS configuration + x.RegisterTLSFlags(flag) + flag.String("tls_server_name", "", "Used to verify the server hostname.") + + initSubcommands() + + var subcommands = []*x.SubCommand{ + &UserAdd, &UserDel, &LogIn, &GroupAdd, &GroupDel, &UserMod, &ChMod, + } + + for _, sc := range subcommands { + Acl.Cmd.AddCommand(sc.Cmd) + sc.Conf = viper.New() + sc.Conf.BindPFlags(sc.Cmd.Flags()) + sc.Conf.BindPFlags(Acl.Cmd.PersistentFlags()) + sc.Conf.SetEnvPrefix(sc.EnvPrefix) + } +} + +func initSubcommands() { + // user creation command + UserAdd.Cmd = &cobra.Command{ + Use: "useradd", + Short: "Run Dgraph acl tool to add a user", + Run: func(cmd *cobra.Command, args []string) { + runTxn(UserAdd.Conf, userAdd) + }, + } + userAddFlags := UserAdd.Cmd.Flags() + userAddFlags.StringP("user", "u", "", "The user id to be created") + userAddFlags.StringP("password", "p", "", "The password for the user") + + // user deletion command + UserDel.Cmd = &cobra.Command{ + Use: "userdel", + Short: "Run Dgraph acl tool to delete a user", + Run: func(cmd *cobra.Command, args []string) { + runTxn(UserDel.Conf, userDel) + }, + } + userDelFlags := UserDel.Cmd.Flags() + userDelFlags.StringP("user", "u", "", "The user id to be deleted") + + // login command + LogIn.Cmd = &cobra.Command{ + Use: "login", + Short: "Login to dgraph in order to get a jwt token", + Run: func(cmd *cobra.Command, args []string) { + runTxn(LogIn.Conf, userLogin) + }, + } + loginFlags := LogIn.Cmd.Flags() + loginFlags.StringP("user", "u", "", "The user id to be created") + loginFlags.StringP("password", "p", "", "The password for the user") + + // group creation command + GroupAdd.Cmd = &cobra.Command{ + Use: "groupadd", + Short: "Run Dgraph acl tool to add a group", + Run: func(cmd *cobra.Command, args []string) { + runTxn(GroupAdd.Conf, groupAdd) + }, + } + groupAddFlags := GroupAdd.Cmd.Flags() + groupAddFlags.StringP("group", "g", "", "The group id to be created") + + // group deletion command + GroupDel.Cmd = &cobra.Command{ + Use: "groupdel", + Short: "Run Dgraph acl tool to delete a group", + Run: func(cmd *cobra.Command, args []string) { + runTxn(GroupDel.Conf, groupDel) + }, + } + groupDelFlags := GroupDel.Cmd.Flags() + groupDelFlags.StringP("group", "g", "", "The group id to be deleted") + + // the usermod command used to set a user's groups + UserMod.Cmd = &cobra.Command{ + Use: "usermod", + Short: "Run Dgraph acl tool to change a user's groups", + Run: func(cmd *cobra.Command, args []string) { + runTxn(UserMod.Conf, userMod) + }, + } + userModFlags := UserMod.Cmd.Flags() + userModFlags.StringP("user", "u", "", "The user id to be changed") + userModFlags.StringP("groups", "G", "", "The groups to be set for the user") + + // the chmod command is used to change a group's permissions + ChMod.Cmd = &cobra.Command{ + Use: "chmod", + Short: "Run Dgraph acl tool to change a group's permissions", + Run: func(cmd *cobra.Command, args []string) { + runTxn(ChMod.Conf, chMod) + }, + } + chModFlags := ChMod.Cmd.Flags() + chModFlags.StringP("group", "g", "", "The group whose permission is to be changed") + chModFlags.StringP("predicate", "p", "", "The predicates whose acls are to be changed") + chModFlags.IntP("acl", "a", 0, "The acl represented using an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") +} + +func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { + opt = options{ + dgraph: conf.GetString("dgraph"), + } + glog.Infof("running transaction with dgraph endpoint: %v", opt.dgraph) + + if len(opt.dgraph) == 0 { + glog.Fatalf("The --dgraph option must be set in order to connect to dgraph") + } + + ds := strings.Split(opt.dgraph, ",") + var clients []api.DgraphClient + var accessClients []api.DgraphAccessClient + for _, d := range ds { + conn, err := x.SetupConnection(d, !tlsConf.CertRequired, &tlsConf, tlsAclCert, tlsAclKey) + x.Checkf(err, "While trying to setup connection to Dgraph alpha.") + defer conn.Close() + + dc := api.NewDgraphClient(conn) + clients = append(clients, dc) + + dgraphAccess := api.NewDgraphAccessClient(conn) + accessClients = append(accessClients, dgraphAccess) + } + dgraphClient := dgo.NewDgraphClient(clients...) + dgraphClient.SetAccessClients(accessClients...) + if err := f(dgraphClient); err != nil { + glog.Errorf("error while running transaction: %v", err) + os.Exit(1) + } +} diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go new file mode 100644 index 00000000000..7602bb46280 --- /dev/null +++ b/ee/acl/cmd/users.go @@ -0,0 +1,283 @@ +package acl + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/dgraph-io/dgo" + "github.com/dgraph-io/dgo/protos/api" + "github.com/dgraph-io/dgraph/x" + "github.com/golang/glog" +) + +func userAdd(dc *dgo.Dgraph) error { + userid := UserAdd.Conf.GetString("user") + password := UserAdd.Conf.GetString("password") + + if len(userid) == 0 { + return fmt.Errorf("The user must not be empty.") + } + if len(password) == 0 { + return fmt.Errorf("The password must not be empty.") + } + + ctx := context.Background() + txn := dc.NewTxn() + defer txn.Discard(ctx) + + dbUser, err := queryDBUser(txn, ctx, userid) + if err != nil { + return err + } + + if dbUser != nil { + return fmt.Errorf("Unable to create user because of conflict: %v", userid) + } + + createUserNQuads := []*api.NQuad{ + { + Subject: "_:" + x.NewEntityLabel, + Predicate: x.Acl_XId, + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: userid}}, + }, + { + Subject: "_:" + x.NewEntityLabel, + Predicate: x.Acl_Password, + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: password}}, + }} + + mu := &api.Mutation{ + CommitNow: true, + Set: createUserNQuads, + } + + _, err = txn.Mutate(ctx, mu) + if err != nil { + return fmt.Errorf("Unable to create user: %v", err) + } + + glog.Infof("Created new user with id %v", userid) + return nil +} + +func userDel(dc *dgo.Dgraph) error { + userid := UserDel.Conf.GetString("user") + // validate the userid + if len(userid) == 0 { + return fmt.Errorf("the user id should not be empty") + } + + ctx := context.Background() + txn := dc.NewTxn() + defer txn.Discard(ctx) + + dbUser, err := queryDBUser(txn, ctx, userid) + if err != nil { + return err + } + + if dbUser == nil || len(dbUser.Uid) == 0 { + return fmt.Errorf("Unable to delete user because it does not exist: %v", userid) + } + + deleteUserNQuads := []*api.NQuad{ + { + Subject: dbUser.Uid, + Predicate: x.Star, + ObjectValue: &api.Value{Val: &api.Value_DefaultVal{DefaultVal: x.Star}}, + }} + + mu := &api.Mutation{ + CommitNow: true, + Del: deleteUserNQuads, + } + + _, err = txn.Mutate(ctx, mu) + if err != nil { + return fmt.Errorf("Unable to delete user: %v", err) + } + + glog.Infof("Deleted user with id %v", userid) + return nil +} + +func userLogin(dc *dgo.Dgraph) error { + userid := UserAdd.Conf.GetString("user") + password := UserAdd.Conf.GetString("password") + + if len(userid) == 0 { + return fmt.Errorf("The user must not be empty.") + } + if len(password) == 0 { + return fmt.Errorf("The password must not be empty.") + } + + ctx := context.Background() + + err := dc.Login(ctx, userid, password) + if err != nil { + return fmt.Errorf("Unable to login:%v", err) + } + glog.Infof("Login successfully with jwt:\n%v", dc.GetJwt()) + return nil +} + +type DBUser struct { + Uid string `json:"uid"` + UserID string `json:"dgraph.xid"` + Password string `json:"dgraph.password"` + Groups []DBGroup `json:"dgraph.user.group"` +} + +func queryDBUser(txn *dgo.Txn, ctx context.Context, userid string) (dbUser *DBUser, err error) { + queryUid := ` + query search($userid: string){ + user(func: eq(` + x.Acl_XId + `, $userid)) { + uid + ` + x.Acl_Password + ` + ` + x.Acl_UserGroup + ` { + uid + dgraph.xid + } + } + }` + + queryVars := make(map[string]string) + queryVars["$userid"] = userid + + queryResp, err := txn.QueryWithVars(ctx, queryUid, queryVars) + if err != nil { + return nil, fmt.Errorf("Error while query user with id %s: %v", userid, err) + } + dbUser, err = UnmarshallDBUser(queryResp, "user") + if err != nil { + return nil, err + } + return dbUser, nil +} + +// Extract the first DBUser pointed by the userKey in the query response +func UnmarshallDBUser(queryResp *api.Response, userKey string) (dbUser *DBUser, err error) { + m := make(map[string][]DBUser) + + err = json.Unmarshal(queryResp.GetJson(), &m) + if err != nil { + return nil, fmt.Errorf("Unable to unmarshal the query user response for user:%v", err) + } + users := m[userKey] + if len(users) == 0 { + // the user does not exist + return nil, nil + } + + return &users[0], nil +} + +func userMod(dc *dgo.Dgraph) error { + userid := UserMod.Conf.GetString("user") + groups := UserMod.Conf.GetString("groups") + if len(userid) == 0 { + return fmt.Errorf("the user must not be empty") + } + + ctx := context.Background() + txn := dc.NewTxn() + defer txn.Discard(ctx) + + dbUser, err := queryDBUser(txn, ctx, userid) + if err != nil { + return err + } + if dbUser == nil { + return fmt.Errorf("The user does not exist: %v", userid) + } + + targetGroupsMap := make(map[string]bool) + if len(groups) > 0 { + for _, g := range strings.Split(groups, ",") { + targetGroupsMap[g] = true + } + } + + existingGroupsMap := make(map[string]bool) + for _, g := range dbUser.Groups { + existingGroupsMap[g.GroupID] = true + } + newGroups, groupsToBeDeleted := calcDiffs(targetGroupsMap, existingGroupsMap) + + mu := &api.Mutation{ + CommitNow: true, + Set: []*api.NQuad{}, + Del: []*api.NQuad{}, + } + + for _, g := range newGroups { + glog.Infof("adding user %v to group %v", userid, g) + nquad, err := getUserModNQuad(txn, ctx, dbUser.Uid, g) + if err != nil { + return fmt.Errorf("error while getting the user mod nquad:%v", err) + } + + mu.Set = append(mu.Set, nquad) + } + + for _, g := range groupsToBeDeleted { + glog.Infof("deleting user %v from group %v", userid, g) + nquad, err := getUserModNQuad(txn, ctx, dbUser.Uid, g) + if err != nil { + return fmt.Errorf("error while getting the user mod nquad:%v", err) + } + mu.Del = append(mu.Del, nquad) + } + if len(mu.Del) == 0 && len(mu.Set) == 0 { + glog.Infof("nothing nees to be changed") + return nil + } + + _, err = txn.Mutate(ctx, mu) + if err != nil { + return err + } + + glog.Infof("Successfully modifed groups for user %v", userid) + return nil +} + +func getUserModNQuad(txn *dgo.Txn, ctx context.Context, useruid string, groupid string) (*api.NQuad, error) { + dbGroup, err := queryDBGroup(txn, ctx, groupid) + if err != nil { + return nil, err + } + if dbGroup == nil { + return nil, fmt.Errorf("the group does not exist:%v", groupid) + } + + createUserGroupNQuads := &api.NQuad{ + Subject: useruid, + Predicate: x.Acl_UserGroup, + ObjectId: dbGroup.Uid, + } + + return createUserGroupNQuads, nil +} + +func calcDiffs(targetMap map[string]bool, existingMap map[string]bool) ([]string, []string) { + + newGroups := []string{} + groupsToBeDeleted := []string{} + + for g, _ := range targetMap { + if _, ok := existingMap[g]; !ok { + newGroups = append(newGroups, g) + } + } + for g, _ := range existingMap { + if _, ok := targetMap[g]; !ok { + groupsToBeDeleted = append(groupsToBeDeleted, g) + } + } + + return newGroups, groupsToBeDeleted +} diff --git a/ee/acl/jwt.go b/ee/acl/jwt.go new file mode 100644 index 00000000000..36eabddabe0 --- /dev/null +++ b/ee/acl/jwt.go @@ -0,0 +1,123 @@ +package acl + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "strings" +) + +type JwtHeader struct { + Alg string // the jwt algorithm + Typ string // the header type "JWT" +} + +var StdJwtHeader = JwtHeader { + Alg: "HS256", + Typ: "JWT", +} + +type JwtGroup struct { + Group string + Wildcardacl string `json:",omitempty"` +} + +type JwtPayload struct { + Userid string + Exp int64 // the unix time sinch epoch + Groups []JwtGroup +} + +type Jwt struct { + Header JwtHeader + Payload JwtPayload +} + +// convert the jwt to string in format xxx.yyy.zzz +// where xxx represents the header, yyy represents the payload, and zzz represents the +// HMAC SHA256 signature signed by the key +func (jwt *Jwt) EncodeToString(key []byte) (string, error) { + if len(key) == 0 { + return "", fmt.Errorf("the key should not be empty") + } + + header, err := json.Marshal(jwt.Header) + if err != nil { + return "", err + } + + payload, err := json.Marshal(jwt.Payload) + if err != nil { + return "", err + } + + // generate the signature + mac := hmac.New(sha256.New, key) + _, err = mac.Write(header) + if err != nil { + return "", err + } + _, err = mac.Write(payload) + if err != nil { + return "", err + } + signature := mac.Sum(nil) + + headerBase64 := base64.StdEncoding.EncodeToString(header) + payloadBase64 := base64.StdEncoding.EncodeToString(payload) + signatureBase64 := base64.StdEncoding.EncodeToString(signature) + return headerBase64 + "." + payloadBase64 + "." + signatureBase64, nil +} + +// Decode the input string into the current Jwt struct, and also verify +// that the signature in the input is valid using the key if checkSignature is true +func (jwt *Jwt) DecodeString(input string, checkSignature bool, key []byte) error { + if len(input) == 0 { + return fmt.Errorf("the input jwt should not be empty") + } + components := strings.Split(input, ".") + if len(components) != 3 { + return fmt.Errorf("input is not in format xxx.yyy.zzz") + } + + header, err := base64.StdEncoding.DecodeString(components[0]) + if err != nil { + return fmt.Errorf("unable to base64 decode the header: %v", components[0]) + } + payload, err := base64.StdEncoding.DecodeString(components[1]) + if err != nil { + return fmt.Errorf("unable to base64 decode the payload: %v", components[1]) + } + + if checkSignature { + if len(key) == 0 { + return fmt.Errorf("the key should not be empty") + } + + signature, err := base64.StdEncoding.DecodeString(components[2]) + if err != nil { + return fmt.Errorf("unable to base64 decode the signature: %v", components[2]) + } + + mac := hmac.New(sha256.New, key) + mac.Write(header) + mac.Write(payload) + expectedSignature := mac.Sum(nil) + if !hmac.Equal(signature, expectedSignature) { + return fmt.Errorf("Signature mismatch") + } + } + + err = json.Unmarshal(header, &jwt.Header) + if err != nil { + return err + } + err = json.Unmarshal(payload, &jwt.Payload) + if err != nil { + return err + } + return nil +} + diff --git a/query/mutation.go b/query/mutation.go index a0c97f53091..3f47142386d 100644 --- a/query/mutation.go +++ b/query/mutation.go @@ -120,6 +120,9 @@ func verifyUid(ctx context.Context, uid uint64) error { return nil } +// AssignUids tries to assign unique ids to each identity in the subjects and objects in the +// format of _:xxx. An identity, e.g. _:a, will only be assigned one uid regardless how many times +// it shows up in the subjects or objects func AssignUids(ctx context.Context, nquads []*api.NQuad) (map[string]uint64, error) { newUids := make(map[string]uint64) num := &pb.Num{} diff --git a/systest/mutations_test.go b/systest/mutations_test.go index 0e539ec102b..81c4e56cdfc 100644 --- a/systest/mutations_test.go +++ b/systest/mutations_test.go @@ -255,8 +255,8 @@ func DeleteAllReverseIndex(t *testing.T, c *dgo.Dgraph) { Running a query would make sure that the previous mutation for creating the link has completed with a commitTs from zero, and the subsequent deletion is done *AFTER* the link creation. - */ - c.NewReadOnlyTxn().Query(ctx, fmt.Sprintf("{ q(func: uid(%s)) { link { uid } }}", aId)) + */ + c.NewReadOnlyTxn().Query(ctx, fmt.Sprintf("{ q(func: uid(%s)) { link { uid } }}", aId)) _, err = c.NewTxn().Mutate(ctx, &api.Mutation{ CommitNow: true, diff --git a/worker/groups.go b/worker/groups.go index bde5fc55c10..e44c78607a9 100644 --- a/worker/groups.go +++ b/worker/groups.go @@ -139,11 +139,40 @@ func StartRaftNodes(walStore *badger.DB, bindall bool) { } func (g *groupi) proposeInitialSchema() { + // propose the schema for _predicate_ if !Config.ExpandEdge { return } + g.upsertSchema(&pb.SchemaUpdate{ + Predicate: x.PredicateListAttr, + ValueType: pb.Posting_STRING, + List: true, + }) + + // propose the schema update for acl predicates + g.upsertSchema(&pb.SchemaUpdate{ + Predicate: x.Acl_XId, + ValueType: pb.Posting_STRING, + Directive: pb.SchemaUpdate_INDEX, + Tokenizer: []string{"exact"}, + }) + + g.upsertSchema(&pb.SchemaUpdate{ + Predicate: x.Acl_Password, + ValueType: pb.Posting_STRING, + }) + + g.upsertSchema(&pb.SchemaUpdate{ + Predicate: x.Acl_UserGroup, + Directive: pb.SchemaUpdate_REVERSE, + ValueType: pb.Posting_UID, + }) + +} + +func (g *groupi) upsertSchema(schema *pb.SchemaUpdate) { g.RLock() - _, ok := g.tablets[x.PredicateListAttr] + _, ok := g.tablets[schema.Predicate] g.RUnlock() if ok { return @@ -153,11 +182,7 @@ func (g *groupi) proposeInitialSchema() { var m pb.Mutations // schema for _predicate_ is not changed once set. m.StartTs = 1 - m.Schema = append(m.Schema, &pb.SchemaUpdate{ - Predicate: x.PredicateListAttr, - ValueType: pb.Posting_STRING, - List: true, - }) + m.Schema = append(m.Schema, schema) // This would propose the schema mutation and make sure some node serves this predicate // and has the schema defined above. diff --git a/x/x.go b/x/x.go index c082d8eecd5..a623ece8e64 100644 --- a/x/x.go +++ b/x/x.go @@ -24,6 +24,7 @@ import ( "math" "net" "net/http" + "path/filepath" "regexp" "sort" "strconv" @@ -31,6 +32,7 @@ import ( "time" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" ) // Error constants representing different types of errors. @@ -68,6 +70,14 @@ const ( ForceAbortDifference = 5000 ) +const ( + NewEntityLabel string = "newid" + Acl_XId string = "dgraph.xid" + Acl_Password string = "dgraph.password" + Acl_UserGroup string = "dgraph.user.group" + Acl_UserBlob string = "dgraph.userblob" +) + var ( // Useful for running multiple servers on the same machine. regExpHostName = regexp.MustCompile(ValidHostnameRegex) @@ -413,3 +423,33 @@ func DivideAndRule(num int) (numGo, width int) { } return } + +func SetupConnection(host string, insecure bool, tlsConf *TLSHelperConfig, + tlsCertFile string, tlsKeyFile string) (*grpc.ClientConn, + error) { + if insecure { + return grpc.Dial(host, + grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(GrpcMaxSize), + grpc.MaxCallSendMsgSize(GrpcMaxSize)), + grpc.WithInsecure(), + grpc.WithBlock(), + grpc.WithTimeout(10*time.Second)) + } + + tlsConf.ConfigType = TLSClientConfig + tlsConf.Cert = filepath.Join(tlsConf.CertDir, tlsCertFile) + tlsConf.Key = filepath.Join(tlsConf.CertDir, tlsKeyFile) + tlsCfg, _, err := GenerateTLSConfig(*tlsConf) + if err != nil { + return nil, err + } + + return grpc.Dial(host, + grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(GrpcMaxSize), + grpc.MaxCallSendMsgSize(GrpcMaxSize)), + grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)), + grpc.WithBlock(), + grpc.WithTimeout(10*time.Second)) +} From 3ec03430cf3722d86b714dba63fbb3622e6f21cd Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Wed, 21 Nov 2018 11:16:56 -0800 Subject: [PATCH 02/30] Formatting using goimports --- dgraph/cmd/root.go | 3 ++- ee/acl/jwt.go | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dgraph/cmd/root.go b/dgraph/cmd/root.go index 8ffac4ef944..72103d3bfbd 100644 --- a/dgraph/cmd/root.go +++ b/dgraph/cmd/root.go @@ -19,9 +19,10 @@ package cmd import ( goflag "flag" "fmt" - "github.com/dgraph-io/dgraph/ee/acl/cmd" "os" + "github.com/dgraph-io/dgraph/ee/acl/cmd" + "github.com/dgraph-io/dgraph/dgraph/cmd/alpha" "github.com/dgraph-io/dgraph/dgraph/cmd/bulk" "github.com/dgraph-io/dgraph/dgraph/cmd/cert" diff --git a/ee/acl/jwt.go b/ee/acl/jwt.go index 36eabddabe0..5d2d99e4a42 100644 --- a/ee/acl/jwt.go +++ b/ee/acl/jwt.go @@ -14,24 +14,24 @@ type JwtHeader struct { Typ string // the header type "JWT" } -var StdJwtHeader = JwtHeader { +var StdJwtHeader = JwtHeader{ Alg: "HS256", Typ: "JWT", } type JwtGroup struct { - Group string + Group string Wildcardacl string `json:",omitempty"` } type JwtPayload struct { Userid string - Exp int64 // the unix time sinch epoch + Exp int64 // the unix time sinch epoch Groups []JwtGroup } type Jwt struct { - Header JwtHeader + Header JwtHeader Payload JwtPayload } @@ -120,4 +120,3 @@ func (jwt *Jwt) DecodeString(input string, checkSignature bool, key []byte) erro } return nil } - From a5c38605c8329f41add3a43ad2cb7af1e7b5d869 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Wed, 21 Nov 2018 11:45:10 -0800 Subject: [PATCH 03/30] Using the correct command for login --- ee/acl/cmd/users.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 7602bb46280..45aae213861 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -104,8 +104,8 @@ func userDel(dc *dgo.Dgraph) error { } func userLogin(dc *dgo.Dgraph) error { - userid := UserAdd.Conf.GetString("user") - password := UserAdd.Conf.GetString("password") + userid := LogIn.Conf.GetString("user") + password := LogIn.Conf.GetString("password") if len(userid) == 0 { return fmt.Errorf("The user must not be empty.") From 2d4b1dbf7cb03819f8816c285cf1d6a76cfc8e97 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 29 Nov 2018 09:53:17 -0800 Subject: [PATCH 04/30] Addressing comments --- dgraph/cmd/alpha/run.go | 11 ++++++--- dgraph/cmd/live/run.go | 6 ++--- dgraph/cmd/root.go | 3 +-- edgraph/server.go | 2 +- ee/acl/accessserver.go | 44 ++++++++++++++++----------------- ee/acl/cmd/groups.go | 30 +++++++++++------------ ee/acl/cmd/run.go | 9 ++++--- ee/acl/cmd/users.go | 54 ++++++++++++++++++++--------------------- ee/acl/jwt.go | 18 ++++++-------- x/tls_helper.go | 8 +++--- x/x.go | 6 +---- 11 files changed, 94 insertions(+), 97 deletions(-) diff --git a/dgraph/cmd/alpha/run.go b/dgraph/cmd/alpha/run.go index 2c38d61d891..0430d4dcdd8 100644 --- a/dgraph/cmd/alpha/run.go +++ b/dgraph/cmd/alpha/run.go @@ -57,6 +57,11 @@ import ( hapi "google.golang.org/grpc/health/grpc_health_v1" ) +const ( + tlsNodeCert = "node.crt" + tlsNodeKey = "node.key" +) + var ( bindall bool tlsConf x.TLSHelperConfig @@ -124,7 +129,7 @@ they form a Raft group and provide synchronous replication. " The token can be passed as follows: For HTTP requests, in X-Dgraph-AuthToken header."+ " For Grpc, in auth-token key in the context.") flag.String("hmac_secret_file", "", "The file storing the HMAC secret"+ - " that is used for signing the JWT") + " that is used for signing the JWT. Enterprise feature.") flag.Float64P("lru_mb", "l", -1, "Estimated memory the LRU cache can take. "+ "Actual usage by the process would be more than specified here.") @@ -402,7 +407,7 @@ func run() { if secretFile != "" { hmacSecret, err := ioutil.ReadFile(secretFile) if err != nil { - glog.Fatalf("unable to read hmac secret from file: %v", secretFile) + glog.Fatalf("Unable to read HMAC secret from file: %v", secretFile) } acl.SetAccessConfiguration(acl.AccessOptions{ @@ -425,7 +430,7 @@ func run() { MaxRetries: Alpha.Conf.GetInt("max_retries"), } - x.LoadTLSConfig(&tlsConf, Alpha.Conf) + x.LoadTLSConfig(&tlsConf, Alpha.Conf, tlsNodeCert, tlsNodeKey) tlsConf.ClientAuth = Alpha.Conf.GetString("tls_client_auth") setupCustomTokenizers() diff --git a/dgraph/cmd/live/run.go b/dgraph/cmd/live/run.go index 8b974e81a87..6011b3c8f91 100644 --- a/dgraph/cmd/live/run.go +++ b/dgraph/cmd/live/run.go @@ -255,7 +255,7 @@ func setup(opts batchMutationOptions, dc *dgo.Dgraph) *loader { kv, err := badger.Open(o) x.Checkf(err, "Error while creating badger KV posting store") - connzero, err := x.SetupConnection(opt.zero, true, &tlsConf, tlsLiveCert, tlsLiveKey) + connzero, err := x.SetupConnection(opt.zero, true, &tlsConf) x.Checkf(err, "Unable to connect to zero, Is it running at %s?", opt.zero) alloc := xidmap.New( @@ -299,7 +299,7 @@ func run() error { ignoreIndexConflict: Live.Conf.GetBool("ignore_index_conflict"), authToken: Live.Conf.GetString("auth_token"), } - x.LoadTLSConfig(&tlsConf, Live.Conf) + x.LoadTLSConfig(&tlsConf, Live.Conf, tlsLiveCert, tlsLiveKey) tlsConf.ServerName = Live.Conf.GetString("tls_server_name") go http.ListenAndServe("localhost:6060", nil) @@ -315,7 +315,7 @@ func run() error { ds := strings.Split(opt.dgraph, ",") var clients []api.DgraphClient for _, d := range ds { - conn, err := x.SetupConnection(d, !tlsConf.CertRequired, &tlsConf, tlsLiveCert, tlsLiveKey) + conn, err := x.SetupConnection(d, !tlsConf.CertRequired, &tlsConf) x.Checkf(err, "While trying to setup connection to Dgraph alpha.") defer conn.Close() diff --git a/dgraph/cmd/root.go b/dgraph/cmd/root.go index 72103d3bfbd..d182163e684 100644 --- a/dgraph/cmd/root.go +++ b/dgraph/cmd/root.go @@ -21,8 +21,6 @@ import ( "fmt" "os" - "github.com/dgraph-io/dgraph/ee/acl/cmd" - "github.com/dgraph-io/dgraph/dgraph/cmd/alpha" "github.com/dgraph-io/dgraph/dgraph/cmd/bulk" "github.com/dgraph-io/dgraph/dgraph/cmd/cert" @@ -31,6 +29,7 @@ import ( "github.com/dgraph-io/dgraph/dgraph/cmd/live" "github.com/dgraph-io/dgraph/dgraph/cmd/version" "github.com/dgraph-io/dgraph/dgraph/cmd/zero" + "github.com/dgraph-io/dgraph/ee/acl/cmd" "github.com/dgraph-io/dgraph/x" "github.com/spf13/cobra" flag "github.com/spf13/pflag" diff --git a/edgraph/server.go b/edgraph/server.go index 4e1d9992376..630fcf8507e 100644 --- a/edgraph/server.go +++ b/edgraph/server.go @@ -602,7 +602,7 @@ func parseNQuads(b []byte) ([]*api.NQuad, error) { // corresponding field of the returned gql.Mutation. For example, the 3 fields, // api.Mutation#SetJson, api.Mutation#SetNquads and api.Mutation#Set are consolidated into the // gql.Mutation.Set field. Similarly the 3 fields api.Mutation#DeleteJson, api.Mutation#DelNquads -// and api.Mutation#Del are merged into the gql.Mutation#Del field +// and api.Mutation#Del are merged into the gql.Mutation#Del field. func parseMutationObject(mu *api.Mutation) (*gql.Mutation, error) { res := &gql.Mutation{} if len(mu.SetJson) > 0 { diff --git a/ee/acl/accessserver.go b/ee/acl/accessserver.go index 5dc8f236453..479afc156d6 100644 --- a/ee/acl/accessserver.go +++ b/ee/acl/accessserver.go @@ -27,35 +27,33 @@ func SetAccessConfiguration(newConfig AccessOptions) { func (accessServer *AccessServer) LogIn(ctx context.Context, request *api.LogInRequest) (*api.LogInResponse, error) { - err := validateLoginRequest(request) - if err != nil { + if err := validateLoginRequest(request); err != nil { return nil, err } resp := &api.LogInResponse{ - Code: api.AclResponseCode_UNAUTHENTICATED, - Context: &api.TxnContext{}, + Code: api.AclResponseCode_UNAUTHENTICATED, } - dbUser, err := queryDBUser(ctx, request.Userid) + user, err := queryUser(ctx, request.Userid) if err != nil { - glog.Infof("Unable to login user with user id: %v", request.Userid) + glog.Warningf("Unable to login user with user id: %v", request.Userid) return nil, err } - if dbUser == nil { - errMsg := fmt.Sprintf("user not found for user id %v", request.Userid) + if user == nil { + errMsg := fmt.Sprintf("User not found for user id %v", request.Userid) glog.Errorf(errMsg) return nil, fmt.Errorf(errMsg) } - if len(dbUser.Password) == 0 { - errMsg := "Unable to authenticate since the user's password is empty" - glog.Errorf(errMsg) + if len(user.Password) == 0 { + errMsg := fmt.Sprintf("Unable to authenticate since the user's password is empty: %v", request.Userid) + glog.Warning(errMsg) return nil, fmt.Errorf(errMsg) } - if dbUser.Password != request.Password { - glog.Infof("Password mismatch for user: %v", request.Userid) + if user.Password != request.Password { + glog.Warningf("Password mismatch for user: %v", request.Userid) resp.Code = api.AclResponseCode_UNAUTHENTICATED return resp, nil } @@ -64,7 +62,7 @@ func (accessServer *AccessServer) LogIn(ctx context.Context, Header: StdJwtHeader, Payload: JwtPayload{ Userid: request.Userid, - Groups: toJwtGroups(dbUser.Groups), + Groups: toJwtGroups(user.Groups), Exp: time.Now().AddDate(0, 0, 30).Unix(), // set the jwt valid for 30 days }, } @@ -91,18 +89,18 @@ func validateLoginRequest(request *api.LogInRequest) error { return nil } -func queryDBUser(ctx context.Context, userid string) (dbUser *acl.DBUser, err error) { - queryUid := ` +func queryUser(ctx context.Context, userid string) (user *acl.User, err error) { + queryUid := fmt.Sprintf(` query search($userid: string){ - user(func: eq(` + x.Acl_XId + `, $userid)) { + user(func: eq(%v, $userid)) { uid, - ` + x.Acl_Password + ` - ` + x.Acl_UserGroup + ` { + %v + %v { uid dgraph.xid } } - }` + }`, x.Acl_XId, x.Acl_Password, x.Acl_UserGroup) queryVars := make(map[string]string) queryVars["$userid"] = userid @@ -116,14 +114,14 @@ func queryDBUser(ctx context.Context, userid string) (dbUser *acl.DBUser, err er glog.Errorf("Error while query user with id %s: %v", userid, err) return nil, err } - dbUser, err = acl.UnmarshallDBUser(queryResp, "user") + user, err = acl.UnmarshallUser(queryResp, "user") if err != nil { return nil, err } - return dbUser, nil + return user, nil } -func toJwtGroups(groups []acl.DBGroup) []JwtGroup { +func toJwtGroups(groups []acl.Group) []JwtGroup { jwtGroups := make([]JwtGroup, len(groups)) for _, g := range groups { diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index b1167402798..7fb2ac5d4da 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -21,12 +21,12 @@ func groupAdd(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - dbGroup, err := queryDBGroup(txn, ctx, groupId) + group, err := queryGroup(txn, ctx, groupId) if err != nil { return err } - if dbGroup != nil { + if group != nil { return fmt.Errorf("The group with id %v already exists.", groupId) } @@ -62,18 +62,18 @@ func groupDel(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - dbGroup, err := queryDBGroup(txn, ctx, groupId) + group, err := queryGroup(txn, ctx, groupId) if err != nil { return err } - if dbGroup == nil || len(dbGroup.Uid) == 0 { + if group == nil || len(group.Uid) == 0 { return fmt.Errorf("Unable to delete group because it does not exist: %v", groupId) } deleteGroupNQuads := []*api.NQuad{ { - Subject: dbGroup.Uid, + Subject: group.Uid, Predicate: x.Star, ObjectValue: &api.Value{Val: &api.Value_DefaultVal{DefaultVal: x.Star}}, }} @@ -91,7 +91,7 @@ func groupDel(dc *dgo.Dgraph) error { return nil } -func queryDBGroup(txn *dgo.Txn, ctx context.Context, groupid string) (dbGroup *DBGroup, err error) { +func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string) (group *Group, err error) { queryUid := ` query search($groupid: string){ group(func: eq(` + x.Acl_XId + `, $groupid)) { @@ -108,16 +108,16 @@ func queryDBGroup(txn *dgo.Txn, ctx context.Context, groupid string) (dbGroup *D glog.Errorf("Error while query group with id %s: %v", groupid, err) return nil, err } - dbGroup, err = UnmarshallDBGroup(queryResp, "group") + group, err = UnmarshallGroup(queryResp, "group") if err != nil { return nil, err } - return dbGroup, nil + return group, nil } -// Extract the first DBUser pointed by the userKey in the query response -func UnmarshallDBGroup(queryResp *api.Response, groupKey string) (dbGroup *DBGroup, err error) { - m := make(map[string][]DBGroup) +// Extract the first User pointed by the userKey in the query response +func UnmarshallGroup(queryResp *api.Response, groupKey string) (group *Group, err error) { + m := make(map[string][]Group) err = json.Unmarshal(queryResp.GetJson(), &m) if err != nil { @@ -134,7 +134,7 @@ func UnmarshallDBGroup(queryResp *api.Response, groupKey string) (dbGroup *DBGro } // parse the response and check existing of the uid -type DBGroup struct { +type Group struct { Uid string `json:"uid"` GroupID string `json:"dgraph.xid"` } @@ -154,16 +154,16 @@ func chMod(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - dbGroup, err := queryDBGroup(txn, ctx, groupId) + group, err := queryGroup(txn, ctx, groupId) if err != nil { return err } - if dbGroup == nil || len(dbGroup.Uid) == 0 { + if group == nil || len(group.Uid) == 0 { return fmt.Errorf("Unable to change permission for group because it does not exist: %v", groupId) } chModNQuads := &api.NQuad{ - Subject: dbGroup.Uid, + Subject: group.Uid, Predicate: predicate, ObjectValue: &api.Value{Val: &api.Value_IntVal{IntVal: acl}}, } diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go index 4fc2e3a3b84..29f80693f24 100644 --- a/ee/acl/cmd/run.go +++ b/ee/acl/cmd/run.go @@ -151,17 +151,20 @@ func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { opt = options{ dgraph: conf.GetString("dgraph"), } - glog.Infof("running transaction with dgraph endpoint: %v", opt.dgraph) + glog.Infof("Running transaction with dgraph endpoint: %v", opt.dgraph) if len(opt.dgraph) == 0 { glog.Fatalf("The --dgraph option must be set in order to connect to dgraph") } + x.LoadTLSConfig(&tlsConf, Acl.Conf, tlsAclCert, tlsAclKey) + tlsConf.ServerName = Acl.Conf.GetString("tls_server_name") + ds := strings.Split(opt.dgraph, ",") var clients []api.DgraphClient var accessClients []api.DgraphAccessClient for _, d := range ds { - conn, err := x.SetupConnection(d, !tlsConf.CertRequired, &tlsConf, tlsAclCert, tlsAclKey) + conn, err := x.SetupConnection(d, !tlsConf.CertRequired, &tlsConf) x.Checkf(err, "While trying to setup connection to Dgraph alpha.") defer conn.Close() @@ -174,7 +177,7 @@ func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { dgraphClient := dgo.NewDgraphClient(clients...) dgraphClient.SetAccessClients(accessClients...) if err := f(dgraphClient); err != nil { - glog.Errorf("error while running transaction: %v", err) + glog.Errorf("Error while running transaction: %v", err) os.Exit(1) } } diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 45aae213861..719a8e52e8f 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -27,12 +27,12 @@ func userAdd(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - dbUser, err := queryDBUser(txn, ctx, userid) + user, err := queryUser(txn, ctx, userid) if err != nil { return err } - if dbUser != nil { + if user != nil { return fmt.Errorf("Unable to create user because of conflict: %v", userid) } @@ -66,25 +66,25 @@ func userDel(dc *dgo.Dgraph) error { userid := UserDel.Conf.GetString("user") // validate the userid if len(userid) == 0 { - return fmt.Errorf("the user id should not be empty") + return fmt.Errorf("The user id should not be empty") } ctx := context.Background() txn := dc.NewTxn() defer txn.Discard(ctx) - dbUser, err := queryDBUser(txn, ctx, userid) + user, err := queryUser(txn, ctx, userid) if err != nil { return err } - if dbUser == nil || len(dbUser.Uid) == 0 { + if user == nil || len(user.Uid) == 0 { return fmt.Errorf("Unable to delete user because it does not exist: %v", userid) } deleteUserNQuads := []*api.NQuad{ { - Subject: dbUser.Uid, + Subject: user.Uid, Predicate: x.Star, ObjectValue: &api.Value{Val: &api.Value_DefaultVal{DefaultVal: x.Star}}, }} @@ -124,14 +124,14 @@ func userLogin(dc *dgo.Dgraph) error { return nil } -type DBUser struct { - Uid string `json:"uid"` - UserID string `json:"dgraph.xid"` - Password string `json:"dgraph.password"` - Groups []DBGroup `json:"dgraph.user.group"` +type User struct { + Uid string `json:"uid"` + UserID string `json:"dgraph.xid"` + Password string `json:"dgraph.password"` + Groups []Group `json:"dgraph.user.group"` } -func queryDBUser(txn *dgo.Txn, ctx context.Context, userid string) (dbUser *DBUser, err error) { +func queryUser(txn *dgo.Txn, ctx context.Context, userid string) (user *User, err error) { queryUid := ` query search($userid: string){ user(func: eq(` + x.Acl_XId + `, $userid)) { @@ -151,16 +151,16 @@ func queryDBUser(txn *dgo.Txn, ctx context.Context, userid string) (dbUser *DBUs if err != nil { return nil, fmt.Errorf("Error while query user with id %s: %v", userid, err) } - dbUser, err = UnmarshallDBUser(queryResp, "user") + user, err = UnmarshallUser(queryResp, "user") if err != nil { return nil, err } - return dbUser, nil + return user, nil } -// Extract the first DBUser pointed by the userKey in the query response -func UnmarshallDBUser(queryResp *api.Response, userKey string) (dbUser *DBUser, err error) { - m := make(map[string][]DBUser) +// Extract the first User pointed by the userKey in the query response +func UnmarshallUser(queryResp *api.Response, userKey string) (user *User, err error) { + m := make(map[string][]User) err = json.Unmarshal(queryResp.GetJson(), &m) if err != nil { @@ -179,18 +179,18 @@ func userMod(dc *dgo.Dgraph) error { userid := UserMod.Conf.GetString("user") groups := UserMod.Conf.GetString("groups") if len(userid) == 0 { - return fmt.Errorf("the user must not be empty") + return fmt.Errorf("The user must not be empty") } ctx := context.Background() txn := dc.NewTxn() defer txn.Discard(ctx) - dbUser, err := queryDBUser(txn, ctx, userid) + user, err := queryUser(txn, ctx, userid) if err != nil { return err } - if dbUser == nil { + if user == nil { return fmt.Errorf("The user does not exist: %v", userid) } @@ -202,7 +202,7 @@ func userMod(dc *dgo.Dgraph) error { } existingGroupsMap := make(map[string]bool) - for _, g := range dbUser.Groups { + for _, g := range user.Groups { existingGroupsMap[g.GroupID] = true } newGroups, groupsToBeDeleted := calcDiffs(targetGroupsMap, existingGroupsMap) @@ -215,7 +215,7 @@ func userMod(dc *dgo.Dgraph) error { for _, g := range newGroups { glog.Infof("adding user %v to group %v", userid, g) - nquad, err := getUserModNQuad(txn, ctx, dbUser.Uid, g) + nquad, err := getUserModNQuad(txn, ctx, user.Uid, g) if err != nil { return fmt.Errorf("error while getting the user mod nquad:%v", err) } @@ -225,7 +225,7 @@ func userMod(dc *dgo.Dgraph) error { for _, g := range groupsToBeDeleted { glog.Infof("deleting user %v from group %v", userid, g) - nquad, err := getUserModNQuad(txn, ctx, dbUser.Uid, g) + nquad, err := getUserModNQuad(txn, ctx, user.Uid, g) if err != nil { return fmt.Errorf("error while getting the user mod nquad:%v", err) } @@ -246,18 +246,18 @@ func userMod(dc *dgo.Dgraph) error { } func getUserModNQuad(txn *dgo.Txn, ctx context.Context, useruid string, groupid string) (*api.NQuad, error) { - dbGroup, err := queryDBGroup(txn, ctx, groupid) + group, err := queryGroup(txn, ctx, groupid) if err != nil { return nil, err } - if dbGroup == nil { - return nil, fmt.Errorf("the group does not exist:%v", groupid) + if group == nil { + return nil, fmt.Errorf("The group does not exist:%v", groupid) } createUserGroupNQuads := &api.NQuad{ Subject: useruid, Predicate: x.Acl_UserGroup, - ObjectId: dbGroup.Uid, + ObjectId: group.Uid, } return createUserGroupNQuads, nil diff --git a/ee/acl/jwt.go b/ee/acl/jwt.go index 5d2d99e4a42..65c55f7d8e9 100644 --- a/ee/acl/jwt.go +++ b/ee/acl/jwt.go @@ -55,12 +55,10 @@ func (jwt *Jwt) EncodeToString(key []byte) (string, error) { // generate the signature mac := hmac.New(sha256.New, key) - _, err = mac.Write(header) - if err != nil { + if _, err := mac.Write(header); err != nil { return "", err } - _, err = mac.Write(payload) - if err != nil { + if _, err := mac.Write(payload); err != nil { return "", err } signature := mac.Sum(nil) @@ -75,30 +73,30 @@ func (jwt *Jwt) EncodeToString(key []byte) (string, error) { // that the signature in the input is valid using the key if checkSignature is true func (jwt *Jwt) DecodeString(input string, checkSignature bool, key []byte) error { if len(input) == 0 { - return fmt.Errorf("the input jwt should not be empty") + return fmt.Errorf("The input jwt should not be empty") } components := strings.Split(input, ".") if len(components) != 3 { - return fmt.Errorf("input is not in format xxx.yyy.zzz") + return fmt.Errorf("Input is not in format xxx.yyy.zzz") } header, err := base64.StdEncoding.DecodeString(components[0]) if err != nil { - return fmt.Errorf("unable to base64 decode the header: %v", components[0]) + return fmt.Errorf("Unable to base64 decode the header: %v", components[0]) } payload, err := base64.StdEncoding.DecodeString(components[1]) if err != nil { - return fmt.Errorf("unable to base64 decode the payload: %v", components[1]) + return fmt.Errorf("Unable to base64 decode the payload: %v", components[1]) } if checkSignature { if len(key) == 0 { - return fmt.Errorf("the key should not be empty") + return fmt.Errorf("The key should not be empty") } signature, err := base64.StdEncoding.DecodeString(components[2]) if err != nil { - return fmt.Errorf("unable to base64 decode the signature: %v", components[2]) + return fmt.Errorf("Unable to base64 decode the signature: %v", components[2]) } mac := hmac.New(sha256.New, key) diff --git a/x/tls_helper.go b/x/tls_helper.go index ff5b10124a3..f556022ab8a 100644 --- a/x/tls_helper.go +++ b/x/tls_helper.go @@ -39,8 +39,6 @@ const ( const ( tlsRootCert = "ca.crt" - tlsNodeCert = "node.crt" - tlsNodeKey = "node.key" ) // TLSHelperConfig define params used to create a tls.Config @@ -61,13 +59,13 @@ func RegisterTLSFlags(flag *pflag.FlagSet) { flag.Bool("tls_use_system_ca", true, "Include System CA into CA Certs.") } -func LoadTLSConfig(conf *TLSHelperConfig, v *viper.Viper) { +func LoadTLSConfig(conf *TLSHelperConfig, v *viper.Viper, tlsCertFile string, tlsKeyFile string) { conf.CertDir = v.GetString("tls_dir") if conf.CertDir != "" { conf.CertRequired = true conf.RootCACert = path.Join(conf.CertDir, tlsRootCert) - conf.Cert = path.Join(conf.CertDir, tlsNodeCert) - conf.Key = path.Join(conf.CertDir, tlsNodeKey) + conf.Cert = path.Join(conf.CertDir, tlsCertFile) + conf.Key = path.Join(conf.CertDir, tlsKeyFile) conf.ClientAuth = v.GetString("tls_client_auth") } conf.UseSystemCACerts = v.GetBool("tls_use_system_ca") diff --git a/x/x.go b/x/x.go index a623ece8e64..f361817f536 100644 --- a/x/x.go +++ b/x/x.go @@ -24,7 +24,6 @@ import ( "math" "net" "net/http" - "path/filepath" "regexp" "sort" "strconv" @@ -424,8 +423,7 @@ func DivideAndRule(num int) (numGo, width int) { return } -func SetupConnection(host string, insecure bool, tlsConf *TLSHelperConfig, - tlsCertFile string, tlsKeyFile string) (*grpc.ClientConn, +func SetupConnection(host string, insecure bool, tlsConf *TLSHelperConfig) (*grpc.ClientConn, error) { if insecure { return grpc.Dial(host, @@ -438,8 +436,6 @@ func SetupConnection(host string, insecure bool, tlsConf *TLSHelperConfig, } tlsConf.ConfigType = TLSClientConfig - tlsConf.Cert = filepath.Join(tlsConf.CertDir, tlsCertFile) - tlsConf.Key = filepath.Join(tlsConf.CertDir, tlsKeyFile) tlsCfg, _, err := GenerateTLSConfig(*tlsConf) if err != nil { return nil, err From 7c524cee623af7595edf8de3ddf4194790264aec Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Wed, 21 Nov 2018 18:19:51 -0800 Subject: [PATCH 05/30] Adding todo for token refresh --- ee/acl/accessserver.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ee/acl/accessserver.go b/ee/acl/accessserver.go index 479afc156d6..09a80a29f9f 100644 --- a/ee/acl/accessserver.go +++ b/ee/acl/accessserver.go @@ -63,7 +63,8 @@ func (accessServer *AccessServer) LogIn(ctx context.Context, Payload: JwtPayload{ Userid: request.Userid, Groups: toJwtGroups(user.Groups), - Exp: time.Now().AddDate(0, 0, 30).Unix(), // set the jwt valid for 30 days + // TODO add the token refresh mechanism and reduce the expiration interval + Exp: time.Now().AddDate(0, 0, 30).Unix(), // set the jwt valid for 30 days }, } From 3c7478fd035c5d6aaff8988d277476632414f7f3 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Mon, 26 Nov 2018 14:15:49 -0800 Subject: [PATCH 06/30] Added opencensus tracing for login and recording client ip --- ee/acl/accessserver.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ee/acl/accessserver.go b/ee/acl/accessserver.go index 09a80a29f9f..87f80fcef58 100644 --- a/ee/acl/accessserver.go +++ b/ee/acl/accessserver.go @@ -10,6 +10,9 @@ import ( "github.com/dgraph-io/dgraph/ee/acl/cmd" "github.com/dgraph-io/dgraph/x" "github.com/golang/glog" + "go.opencensus.io/trace" + otrace "go.opencensus.io/trace" + "google.golang.org/grpc/peer" ) // Server implements protos.DgraphAccessServer @@ -27,6 +30,17 @@ func SetAccessConfiguration(newConfig AccessOptions) { func (accessServer *AccessServer) LogIn(ctx context.Context, request *api.LogInRequest) (*api.LogInResponse, error) { + ctx, span := otrace.StartSpan(ctx, "accessserver.LogIn") + defer span.End() + + // record the client ip for this login request + clientPeer, ok := peer.FromContext(ctx) + if ok { + span.Annotate([]trace.Attribute{ + trace.StringAttribute("client_ip", clientPeer.Addr.String()), + }, "peer info for acl login") + } + if err := validateLoginRequest(request); err != nil { return nil, err } From 8165f83363c1e3024743ddba1450d329215c9c87 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Mon, 26 Nov 2018 15:44:12 -0800 Subject: [PATCH 07/30] Refactored the code to checkpassword and inlining the predicate names --- ee/acl/accessserver.go | 45 +++++++++++++++++++++--------------------- ee/acl/cmd/groups.go | 8 ++++---- ee/acl/cmd/users.go | 24 +++++++++++----------- worker/groups.go | 8 ++++---- x/x.go | 8 -------- 5 files changed, 42 insertions(+), 51 deletions(-) diff --git a/ee/acl/accessserver.go b/ee/acl/accessserver.go index 87f80fcef58..a1004a1ad46 100644 --- a/ee/acl/accessserver.go +++ b/ee/acl/accessserver.go @@ -8,7 +8,6 @@ import ( "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/edgraph" "github.com/dgraph-io/dgraph/ee/acl/cmd" - "github.com/dgraph-io/dgraph/x" "github.com/golang/glog" "go.opencensus.io/trace" otrace "go.opencensus.io/trace" @@ -46,32 +45,26 @@ func (accessServer *AccessServer) LogIn(ctx context.Context, } resp := &api.LogInResponse{ - Code: api.AclResponseCode_UNAUTHENTICATED, + Context: &api.TxnContext{}, + Code: api.AclResponseCode_UNAUTHENTICATED, } - user, err := queryUser(ctx, request.Userid) + user, err := queryUser(ctx, request.Userid, request.Password) if err != nil { glog.Warningf("Unable to login user with user id: %v", request.Userid) return nil, err } if user == nil { errMsg := fmt.Sprintf("User not found for user id %v", request.Userid) - glog.Errorf(errMsg) + glog.Warningf(errMsg) return nil, fmt.Errorf(errMsg) } - - if len(user.Password) == 0 { - errMsg := fmt.Sprintf("Unable to authenticate since the user's password is empty: %v", request.Userid) - glog.Warning(errMsg) + if !user.PasswordMatch { + errMsg := fmt.Sprintf("Password mismatch for user: %v", request.Userid) + glog.Warningf(errMsg) return nil, fmt.Errorf(errMsg) } - if user.Password != request.Password { - glog.Warningf("Password mismatch for user: %v", request.Userid) - resp.Code = api.AclResponseCode_UNAUTHENTICATED - return resp, nil - } - jwt := &Jwt{ Header: StdJwtHeader, Payload: JwtPayload{ @@ -87,6 +80,7 @@ func (accessServer *AccessServer) LogIn(ctx context.Context, glog.Errorf("Unable to encode jwt to string: %v", err) return nil, err } + resp.Code = api.AclResponseCode_OK return resp, nil } @@ -104,21 +98,22 @@ func validateLoginRequest(request *api.LogInRequest) error { return nil } -func queryUser(ctx context.Context, userid string) (user *acl.User, err error) { - queryUid := fmt.Sprintf(` - query search($userid: string){ - user(func: eq(%v, $userid)) { - uid, - %v - %v { +func queryUser(ctx context.Context, userid string, password string) (user *acl.User, err error) { + queryUid := ` + query search($userid: string, $password: string){ + user(func: eq(dgraph.xid, $userid)) { + uid + password_match: checkpwd(dgraph.password, $password) + dgraph.user.group { uid dgraph.xid } } - }`, x.Acl_XId, x.Acl_Password, x.Acl_UserGroup) + }` queryVars := make(map[string]string) queryVars["$userid"] = userid + queryVars["$password"] = password queryRequest := api.Request{ Query: queryUid, Vars: queryVars, @@ -137,8 +132,12 @@ func queryUser(ctx context.Context, userid string) (user *acl.User, err error) { } func toJwtGroups(groups []acl.Group) []JwtGroup { - jwtGroups := make([]JwtGroup, len(groups)) + if groups == nil { + // the user does not have any groups + return nil + } + jwtGroups := make([]JwtGroup, len(groups)) for _, g := range groups { jwtGroups = append(jwtGroups, JwtGroup{ Group: g.GroupID, diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 7fb2ac5d4da..63205bf6056 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -32,8 +32,8 @@ func groupAdd(dc *dgo.Dgraph) error { createGroupNQuads := []*api.NQuad{ { - Subject: "_:" + x.NewEntityLabel, - Predicate: x.Acl_XId, + Subject: "_:newgroup", + Predicate: "dgraph.xid", ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: groupId}}, }, } @@ -94,9 +94,9 @@ func groupDel(dc *dgo.Dgraph) error { func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string) (group *Group, err error) { queryUid := ` query search($groupid: string){ - group(func: eq(` + x.Acl_XId + `, $groupid)) { + group(func: eq(dgraph.xid, $groupid)) { uid, - ` + x.Acl_XId + ` + dgraph.xid } }` diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 719a8e52e8f..04edfea4421 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -38,13 +38,13 @@ func userAdd(dc *dgo.Dgraph) error { createUserNQuads := []*api.NQuad{ { - Subject: "_:" + x.NewEntityLabel, - Predicate: x.Acl_XId, + Subject: "_:newuser", + Predicate: "dgraph.xid", ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: userid}}, }, { - Subject: "_:" + x.NewEntityLabel, - Predicate: x.Acl_Password, + Subject: "_:newuser", + Predicate: "dgraph.password", ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: password}}, }} @@ -125,19 +125,19 @@ func userLogin(dc *dgo.Dgraph) error { } type User struct { - Uid string `json:"uid"` - UserID string `json:"dgraph.xid"` - Password string `json:"dgraph.password"` - Groups []Group `json:"dgraph.user.group"` + Uid string `json:"uid"` + UserID string `json:"dgraph.xid"` + Password string `json:"dgraph.password"` + PasswordMatch bool `json:"password_match"` + Groups []Group `json:"dgraph.user.group"` } func queryUser(txn *dgo.Txn, ctx context.Context, userid string) (user *User, err error) { queryUid := ` query search($userid: string){ - user(func: eq(` + x.Acl_XId + `, $userid)) { + user(func: eq(dgraph.xid, $userid)) { uid - ` + x.Acl_Password + ` - ` + x.Acl_UserGroup + ` { + dgraph.user.group { uid dgraph.xid } @@ -256,7 +256,7 @@ func getUserModNQuad(txn *dgo.Txn, ctx context.Context, useruid string, groupid createUserGroupNQuads := &api.NQuad{ Subject: useruid, - Predicate: x.Acl_UserGroup, + Predicate: "dgraph.user.group", ObjectId: group.Uid, } diff --git a/worker/groups.go b/worker/groups.go index e44c78607a9..590cf1b251a 100644 --- a/worker/groups.go +++ b/worker/groups.go @@ -151,19 +151,19 @@ func (g *groupi) proposeInitialSchema() { // propose the schema update for acl predicates g.upsertSchema(&pb.SchemaUpdate{ - Predicate: x.Acl_XId, + Predicate: "dgraph.xid", ValueType: pb.Posting_STRING, Directive: pb.SchemaUpdate_INDEX, Tokenizer: []string{"exact"}, }) g.upsertSchema(&pb.SchemaUpdate{ - Predicate: x.Acl_Password, - ValueType: pb.Posting_STRING, + Predicate: "dgraph.password", + ValueType: pb.Posting_PASSWORD, }) g.upsertSchema(&pb.SchemaUpdate{ - Predicate: x.Acl_UserGroup, + Predicate: "dgraph.user.group", Directive: pb.SchemaUpdate_REVERSE, ValueType: pb.Posting_UID, }) diff --git a/x/x.go b/x/x.go index f361817f536..847091818ee 100644 --- a/x/x.go +++ b/x/x.go @@ -69,14 +69,6 @@ const ( ForceAbortDifference = 5000 ) -const ( - NewEntityLabel string = "newid" - Acl_XId string = "dgraph.xid" - Acl_Password string = "dgraph.password" - Acl_UserGroup string = "dgraph.user.group" - Acl_UserBlob string = "dgraph.userblob" -) - var ( // Useful for running multiple servers on the same machine. regExpHostName = regexp.MustCompile(ValidHostnameRegex) From a7932be4018d418268c9db980d503981120f0423 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Mon, 26 Nov 2018 15:47:29 -0800 Subject: [PATCH 08/30] Capitalizing the log messages --- ee/acl/cmd/users.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 04edfea4421..45e3dd1db8a 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -214,7 +214,7 @@ func userMod(dc *dgo.Dgraph) error { } for _, g := range newGroups { - glog.Infof("adding user %v to group %v", userid, g) + glog.Infof("Adding user %v to group %v", userid, g) nquad, err := getUserModNQuad(txn, ctx, user.Uid, g) if err != nil { return fmt.Errorf("error while getting the user mod nquad:%v", err) @@ -224,7 +224,7 @@ func userMod(dc *dgo.Dgraph) error { } for _, g := range groupsToBeDeleted { - glog.Infof("deleting user %v from group %v", userid, g) + glog.Infof("Deleting user %v from group %v", userid, g) nquad, err := getUserModNQuad(txn, ctx, user.Uid, g) if err != nil { return fmt.Errorf("error while getting the user mod nquad:%v", err) @@ -232,7 +232,7 @@ func userMod(dc *dgo.Dgraph) error { mu.Del = append(mu.Del, nquad) } if len(mu.Del) == 0 && len(mu.Set) == 0 { - glog.Infof("nothing nees to be changed") + glog.Infof("Nothing nees to be changed for the groups of user:%v", userid) return nil } From 5808aa58d8ce61bc7649b5f232e298ddeeb1034b Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Mon, 26 Nov 2018 17:38:46 -0800 Subject: [PATCH 09/30] Storing all the acls inside a blob --- dgraph/cmd/root.go | 2 +- ee/acl/cmd/groups.go | 89 +++++++++++++++++++++++++++++++++++--------- ee/acl/cmd/run.go | 18 ++++----- ee/acl/cmd/users.go | 2 +- worker/groups.go | 5 ++- 5 files changed, 87 insertions(+), 29 deletions(-) diff --git a/dgraph/cmd/root.go b/dgraph/cmd/root.go index d182163e684..c392f9e3020 100644 --- a/dgraph/cmd/root.go +++ b/dgraph/cmd/root.go @@ -87,7 +87,7 @@ func init() { var subcommands = []*x.SubCommand{ &bulk.Bulk, &cert.Cert, &conv.Conv, &live.Live, &alpha.Alpha, &zero.Zero, - &version.Version, &debug.Debug, &acl.Acl, + &version.Version, &debug.Debug, &acl.CmdAcl, } for _, sc := range subcommands { RootCmd.AddCommand(sc.Cmd) diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 63205bf6056..858c6cf9457 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -1,6 +1,7 @@ package acl import ( + "bytes" "context" "encoding/json" "fmt" @@ -21,7 +22,7 @@ func groupAdd(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - group, err := queryGroup(txn, ctx, groupId) + group, err := queryGroup(txn, ctx, groupId, []string{"uid"}) if err != nil { return err } @@ -62,7 +63,7 @@ func groupDel(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - group, err := queryGroup(txn, ctx, groupId) + group, err := queryGroup(txn, ctx, groupId, []string{"uid"}) if err != nil { return err } @@ -91,14 +92,24 @@ func groupDel(dc *dgo.Dgraph) error { return nil } -func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string) (group *Group, err error) { - queryUid := ` - query search($groupid: string){ - group(func: eq(dgraph.xid, $groupid)) { - uid, - dgraph.xid +func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string, fields []string) (group *Group, err error) { + var queryBuilder bytes.Buffer + // write query header + queryBuilder.WriteString(` + query search($groupid: string){ + group(func: eq(dgraph.xid, $groupid)) { + `) + + for _, f := range fields { + queryBuilder.WriteString(f) + queryBuilder.WriteString("\n") + } + // write query footer + queryBuilder.WriteString(` } - }` + }`) + + queryUid := queryBuilder.String() queryVars := make(map[string]string) queryVars["$groupid"] = groupid @@ -121,7 +132,7 @@ func UnmarshallGroup(queryResp *api.Response, groupKey string) (group *Group, er err = json.Unmarshal(queryResp.GetJson(), &m) if err != nil { - glog.Errorf("Unable to unmarshal the query group response") + glog.Errorf("Unable to unmarshal the query group response:%v", err) return nil, err } groups := m[groupKey] @@ -133,16 +144,22 @@ func UnmarshallGroup(queryResp *api.Response, groupKey string) (group *Group, er return &groups[0], nil } +type Acl struct { + Predicate string `json:"predicate"` + Perm uint32 `json:"perm"` +} + // parse the response and check existing of the uid type Group struct { Uid string `json:"uid"` GroupID string `json:"dgraph.xid"` + Acls string `json:"dgraph.group.acl"` } func chMod(dc *dgo.Dgraph) error { groupId := ChMod.Conf.GetString("group") - predicate := ChMod.Conf.GetString("predicate") - acl := ChMod.Conf.GetInt64("acl") + predicate := ChMod.Conf.GetString("pred") + perm := uint32(ChMod.Conf.GetInt("perm")) if len(groupId) == 0 { return fmt.Errorf("the groupid must not be empty") } @@ -154,7 +171,7 @@ func chMod(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - group, err := queryGroup(txn, ctx, groupId) + group, err := queryGroup(txn, ctx, groupId, []string{"uid", "dgraph.group.acl"}) if err != nil { return err } @@ -162,10 +179,32 @@ func chMod(dc *dgo.Dgraph) error { if group == nil || len(group.Uid) == 0 { return fmt.Errorf("Unable to change permission for group because it does not exist: %v", groupId) } + + currentAcls := []Acl{} + if len(group.Acls) != 0 { + if err := json.Unmarshal([]byte(group.Acls), ¤tAcls); err != nil { + return fmt.Errorf("Unable to unmarshal the acls associated with the group:%v", groupId) + } + } + + newAcls, updated := addAcl(currentAcls, &Acl{ + Predicate: predicate, + Perm: perm, + }) + if !updated { + glog.Infof("Nothing needs to be changed for the permission of group:%v", groupId) + return nil + } + + newAclBytes, err := json.Marshal(newAcls) + if err != nil { + return fmt.Errorf("Unable to marshal the updated acls:%v", err) + } + chModNQuads := &api.NQuad{ Subject: group.Uid, - Predicate: predicate, - ObjectValue: &api.Value{Val: &api.Value_IntVal{IntVal: acl}}, + Predicate: "dgraph.group.acl", + ObjectValue: &api.Value{Val: &api.Value_BytesVal{BytesVal: newAclBytes}}, } mu := &api.Mutation{ CommitNow: true, @@ -174,8 +213,24 @@ func chMod(dc *dgo.Dgraph) error { _, err = txn.Mutate(ctx, mu) if err != nil { - return fmt.Errorf("unable to change mutations for the group %v on predicate %v: %v", groupId, predicate, err) + return fmt.Errorf("Unable to change mutations for the group %v on predicate %v: %v", groupId, predicate, err) } - glog.Infof("Successfully changed permission for group %v on predicate %v to %v", groupId, predicate, acl) + glog.Infof("Successfully changed permission for group %v on predicate %v to %v", groupId, predicate, perm) return nil } + +// returns whether the existing acls slice is changed +func addAcl(acls []Acl, newAcl *Acl) ([]Acl, bool) { + for idx, acl := range acls { + if acl.Predicate == newAcl.Predicate { + if acl.Perm == newAcl.Perm { + return acls, false + } + acls[idx].Perm = newAcl.Perm + return acls, true + } + } + + // we do not find any existing acl matching the newAcl predicate + return append(acls, *newAcl), true +} diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go index 29f80693f24..6d84f446b28 100644 --- a/ee/acl/cmd/run.go +++ b/ee/acl/cmd/run.go @@ -19,7 +19,7 @@ type options struct { var opt options var tlsConf x.TLSHelperConfig -var Acl x.SubCommand +var CmdAcl x.SubCommand var UserAdd x.SubCommand var UserDel x.SubCommand var LogIn x.SubCommand @@ -36,12 +36,12 @@ const ( ) func init() { - Acl.Cmd = &cobra.Command{ + CmdAcl.Cmd = &cobra.Command{ Use: "acl", Short: "Run the Dgraph acl tool", } - flag := Acl.Cmd.PersistentFlags() + flag := CmdAcl.Cmd.PersistentFlags() flag.StringP("dgraph", "d", "127.0.0.1:9080", "Dgraph gRPC server address") // TLS configuration @@ -55,10 +55,10 @@ func init() { } for _, sc := range subcommands { - Acl.Cmd.AddCommand(sc.Cmd) + CmdAcl.Cmd.AddCommand(sc.Cmd) sc.Conf = viper.New() sc.Conf.BindPFlags(sc.Cmd.Flags()) - sc.Conf.BindPFlags(Acl.Cmd.PersistentFlags()) + sc.Conf.BindPFlags(CmdAcl.Cmd.PersistentFlags()) sc.Conf.SetEnvPrefix(sc.EnvPrefix) } } @@ -143,8 +143,8 @@ func initSubcommands() { } chModFlags := ChMod.Cmd.Flags() chModFlags.StringP("group", "g", "", "The group whose permission is to be changed") - chModFlags.StringP("predicate", "p", "", "The predicates whose acls are to be changed") - chModFlags.IntP("acl", "a", 0, "The acl represented using an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") + chModFlags.StringP("pred", "p", "", "The predicates whose acls are to be changed") + chModFlags.IntP("perm", "P", 0, "The acl represented using an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") } func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { @@ -157,8 +157,8 @@ func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { glog.Fatalf("The --dgraph option must be set in order to connect to dgraph") } - x.LoadTLSConfig(&tlsConf, Acl.Conf, tlsAclCert, tlsAclKey) - tlsConf.ServerName = Acl.Conf.GetString("tls_server_name") + x.LoadTLSConfig(&tlsConf, CmdAcl.Conf, tlsAclCert, tlsAclKey) + tlsConf.ServerName = CmdAcl.Conf.GetString("tls_server_name") ds := strings.Split(opt.dgraph, ",") var clients []api.DgraphClient diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 45e3dd1db8a..4dba74979ff 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -246,7 +246,7 @@ func userMod(dc *dgo.Dgraph) error { } func getUserModNQuad(txn *dgo.Txn, ctx context.Context, useruid string, groupid string) (*api.NQuad, error) { - group, err := queryGroup(txn, ctx, groupid) + group, err := queryGroup(txn, ctx, groupid, []string{"uid"}) if err != nil { return nil, err } diff --git a/worker/groups.go b/worker/groups.go index 590cf1b251a..a05fb5a3a94 100644 --- a/worker/groups.go +++ b/worker/groups.go @@ -167,7 +167,10 @@ func (g *groupi) proposeInitialSchema() { Directive: pb.SchemaUpdate_REVERSE, ValueType: pb.Posting_UID, }) - + g.upsertSchema(&pb.SchemaUpdate{ + Predicate: "dgraph.group.acl", + ValueType: pb.Posting_STRING, + }) } func (g *groupi) upsertSchema(schema *pb.SchemaUpdate) { From f0003c4244abac4c00b7a6e82f988332d94135d2 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Mon, 26 Nov 2018 17:51:03 -0800 Subject: [PATCH 10/30] Supports removing acls with negative perms --- ee/acl/cmd/groups.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 858c6cf9457..421840ce28b 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -146,7 +146,7 @@ func UnmarshallGroup(queryResp *api.Response, groupKey string) (group *Group, er type Acl struct { Predicate string `json:"predicate"` - Perm uint32 `json:"perm"` + Perm int32 `json:"perm"` } // parse the response and check existing of the uid @@ -159,7 +159,7 @@ type Group struct { func chMod(dc *dgo.Dgraph) error { groupId := ChMod.Conf.GetString("group") predicate := ChMod.Conf.GetString("pred") - perm := uint32(ChMod.Conf.GetInt("perm")) + perm := ChMod.Conf.GetInt("perm") if len(groupId) == 0 { return fmt.Errorf("the groupid must not be empty") } @@ -187,9 +187,9 @@ func chMod(dc *dgo.Dgraph) error { } } - newAcls, updated := addAcl(currentAcls, &Acl{ + newAcls, updated := updateAcl(currentAcls, &Acl{ Predicate: predicate, - Perm: perm, + Perm: int32(perm), }) if !updated { glog.Infof("Nothing needs to be changed for the permission of group:%v", groupId) @@ -220,12 +220,17 @@ func chMod(dc *dgo.Dgraph) error { } // returns whether the existing acls slice is changed -func addAcl(acls []Acl, newAcl *Acl) ([]Acl, bool) { +func updateAcl(acls []Acl, newAcl *Acl) ([]Acl, bool) { for idx, acl := range acls { if acl.Predicate == newAcl.Predicate { if acl.Perm == newAcl.Perm { return acls, false } + if newAcl.Perm < 0 { + // remove the current acl from the array + copy(acls[idx:], acls[idx+1:]) + return acls[:len(acls)-1], true + } acls[idx].Perm = newAcl.Perm return acls, true } From 3c83edb11df8918e85d133e38f726f5e96b135bf Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Mon, 26 Nov 2018 18:08:21 -0800 Subject: [PATCH 11/30] Support configurable jwt ttl --- dgraph/cmd/alpha/run.go | 2 ++ ee/acl/accessserver.go | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dgraph/cmd/alpha/run.go b/dgraph/cmd/alpha/run.go index 0430d4dcdd8..d8c491734a7 100644 --- a/dgraph/cmd/alpha/run.go +++ b/dgraph/cmd/alpha/run.go @@ -130,6 +130,7 @@ they form a Raft group and provide synchronous replication. " For Grpc, in auth-token key in the context.") flag.String("hmac_secret_file", "", "The file storing the HMAC secret"+ " that is used for signing the JWT. Enterprise feature.") + flag.Duration("jwt_ttl", 6*time.Hour, "The TTL of jwt tokens. Enterprise feature.") flag.Float64P("lru_mb", "l", -1, "Estimated memory the LRU cache can take. "+ "Actual usage by the process would be more than specified here.") @@ -412,6 +413,7 @@ func run() { acl.SetAccessConfiguration(acl.AccessOptions{ HmacSecret: hmacSecret, + JwtTtl: Alpha.Conf.GetDuration("jwt_ttl"), }) glog.Info("HMAC secret loaded successfully.") } diff --git a/ee/acl/accessserver.go b/ee/acl/accessserver.go index a1004a1ad46..348f23ca00a 100644 --- a/ee/acl/accessserver.go +++ b/ee/acl/accessserver.go @@ -19,6 +19,7 @@ type AccessServer struct{} type AccessOptions struct { HmacSecret []byte + JwtTtl time.Duration } var accessConfig AccessOptions @@ -71,7 +72,7 @@ func (accessServer *AccessServer) LogIn(ctx context.Context, Userid: request.Userid, Groups: toJwtGroups(user.Groups), // TODO add the token refresh mechanism and reduce the expiration interval - Exp: time.Now().AddDate(0, 0, 30).Unix(), // set the jwt valid for 30 days + Exp: time.Now().Add(accessConfig.JwtTtl).Unix(), // set the jwt valid for 30 days }, } From 5b586f91d918ff72a4bb5a5d47b19b3844cb8790 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Tue, 27 Nov 2018 10:43:34 -0800 Subject: [PATCH 12/30] Polishing the pr --- dgraph/cmd/alpha/run.go | 3 +-- ee/acl/accessserver.go | 9 ++++----- ee/acl/acl_test.go | 14 +++++++------- ee/acl/cmd/groups.go | 6 +++--- ee/acl/cmd/users.go | 4 ++-- ee/acl/jwt.go | 3 +-- 6 files changed, 18 insertions(+), 21 deletions(-) diff --git a/dgraph/cmd/alpha/run.go b/dgraph/cmd/alpha/run.go index d8c491734a7..c645a70de4f 100644 --- a/dgraph/cmd/alpha/run.go +++ b/dgraph/cmd/alpha/run.go @@ -33,10 +33,9 @@ import ( "syscall" "time" - "github.com/dgraph-io/dgraph/ee/acl" - "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/edgraph" + "github.com/dgraph-io/dgraph/ee/acl" "github.com/dgraph-io/dgraph/posting" "github.com/dgraph-io/dgraph/schema" "github.com/dgraph-io/dgraph/tok" diff --git a/ee/acl/accessserver.go b/ee/acl/accessserver.go index 348f23ca00a..0a6f31e8461 100644 --- a/ee/acl/accessserver.go +++ b/ee/acl/accessserver.go @@ -38,7 +38,7 @@ func (accessServer *AccessServer) LogIn(ctx context.Context, if ok { span.Annotate([]trace.Attribute{ trace.StringAttribute("client_ip", clientPeer.Addr.String()), - }, "peer info for acl login") + }, "client ip for login") } if err := validateLoginRequest(request); err != nil { @@ -46,7 +46,7 @@ func (accessServer *AccessServer) LogIn(ctx context.Context, } resp := &api.LogInResponse{ - Context: &api.TxnContext{}, + Context: &api.TxnContext{}, // context needed in order to set the jwt below Code: api.AclResponseCode_UNAUTHENTICATED, } @@ -71,7 +71,7 @@ func (accessServer *AccessServer) LogIn(ctx context.Context, Payload: JwtPayload{ Userid: request.Userid, Groups: toJwtGroups(user.Groups), - // TODO add the token refresh mechanism and reduce the expiration interval + // TODO add the token refresh mechanism Exp: time.Now().Add(accessConfig.JwtTtl).Unix(), // set the jwt valid for 30 days }, } @@ -141,8 +141,7 @@ func toJwtGroups(groups []acl.Group) []JwtGroup { jwtGroups := make([]JwtGroup, len(groups)) for _, g := range groups { jwtGroups = append(jwtGroups, JwtGroup{ - Group: g.GroupID, - Wildcardacl: "", // TODO set it to the wild card acl returned from DB + Group: g.GroupID, }) } return jwtGroups diff --git a/ee/acl/acl_test.go b/ee/acl/acl_test.go index 6711138ab26..e7e9dcc0ab9 100644 --- a/ee/acl/acl_test.go +++ b/ee/acl/acl_test.go @@ -27,26 +27,26 @@ func checkOutput(t *testing.T, cmd *exec.Cmd, shouldFail bool) string { } func CreateAndDeleteUsers(t *testing.T) { - createUserCmd1 := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", "lucas", - "-p", "haha") + createUserCmd1 := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", userid, + "-p", userpassword) createUserOutput1 := checkOutput(t, createUserCmd1, false) t.Logf("Got output when creating user:%v", createUserOutput1) - createUserCmd2 := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", "lucas", - "-p", "haha") + createUserCmd2 := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", userid, + "-p", userpassword) // create the user again should fail createUserOutput2 := checkOutput(t, createUserCmd2, true) t.Logf("Got output when creating user:%v", createUserOutput2) // delete the user - deleteUserCmd := exec.Command("dgraph", "acl", "userdel", "-d", dgraphEndpoint, "-u", "lucas") + deleteUserCmd := exec.Command("dgraph", "acl", "userdel", "-d", dgraphEndpoint, "-u", userid) deleteUserOutput := checkOutput(t, deleteUserCmd, false) t.Logf("Got output when deleting user:%v", deleteUserOutput) // now we should be able to create the user again - createUserCmd3 := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", "lucas", - "-p", "haha") + createUserCmd3 := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", userid, + "-p", userpassword) createUserOutput3 := checkOutput(t, createUserCmd3, false) t.Logf("Got output when creating user:%v", createUserOutput3) } diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 421840ce28b..092bd332781 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -109,12 +109,12 @@ func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string, fields []stri } }`) - queryUid := queryBuilder.String() + query := queryBuilder.String() queryVars := make(map[string]string) queryVars["$groupid"] = groupid - queryResp, err := txn.QueryWithVars(ctx, queryUid, queryVars) + queryResp, err := txn.QueryWithVars(ctx, query, queryVars) if err != nil { glog.Errorf("Error while query group with id %s: %v", groupid, err) return nil, err @@ -183,7 +183,7 @@ func chMod(dc *dgo.Dgraph) error { currentAcls := []Acl{} if len(group.Acls) != 0 { if err := json.Unmarshal([]byte(group.Acls), ¤tAcls); err != nil { - return fmt.Errorf("Unable to unmarshal the acls associated with the group:%v", groupId) + return fmt.Errorf("Unable to unmarshal the acls associated with the group %v:%v", groupId, err) } } diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 4dba74979ff..2d56d7c2b40 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -133,7 +133,7 @@ type User struct { } func queryUser(txn *dgo.Txn, ctx context.Context, userid string) (user *User, err error) { - queryUid := ` + query := ` query search($userid: string){ user(func: eq(dgraph.xid, $userid)) { uid @@ -147,7 +147,7 @@ func queryUser(txn *dgo.Txn, ctx context.Context, userid string) (user *User, er queryVars := make(map[string]string) queryVars["$userid"] = userid - queryResp, err := txn.QueryWithVars(ctx, queryUid, queryVars) + queryResp, err := txn.QueryWithVars(ctx, query, queryVars) if err != nil { return nil, fmt.Errorf("Error while query user with id %s: %v", userid, err) } diff --git a/ee/acl/jwt.go b/ee/acl/jwt.go index 65c55f7d8e9..1f2f0306c3e 100644 --- a/ee/acl/jwt.go +++ b/ee/acl/jwt.go @@ -20,8 +20,7 @@ var StdJwtHeader = JwtHeader{ } type JwtGroup struct { - Group string - Wildcardacl string `json:",omitempty"` + Group string } type JwtPayload struct { From 54a255b166855b6bfbda16c86fb7c2e02b1614bf Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 29 Nov 2018 11:15:29 -0800 Subject: [PATCH 13/30] Addressing comments --- dgraph/cmd/alpha/run.go | 14 ++-- dgraph/cmd/live/run.go | 7 +- edgraph/access_oss.go | 16 +++++ edgraph/config.go | 4 ++ ee/acl/accessserver.go | 148 ---------------------------------------- ee/acl/acl_test.go | 12 ++++ ee/acl/cmd/groups.go | 41 +++++------ ee/acl/cmd/run.go | 35 ++++++---- ee/acl/cmd/users.go | 86 ++++++++--------------- ee/acl/jwt.go | 12 ++++ oss/errors.go | 7 ++ worker/backup_oss.go | 9 ++- x/x.go | 22 ++++++ 13 files changed, 149 insertions(+), 264 deletions(-) create mode 100644 edgraph/access_oss.go delete mode 100644 ee/acl/accessserver.go create mode 100644 oss/errors.go diff --git a/dgraph/cmd/alpha/run.go b/dgraph/cmd/alpha/run.go index c645a70de4f..b546adf4b9f 100644 --- a/dgraph/cmd/alpha/run.go +++ b/dgraph/cmd/alpha/run.go @@ -35,7 +35,6 @@ import ( "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/edgraph" - "github.com/dgraph-io/dgraph/ee/acl" "github.com/dgraph-io/dgraph/posting" "github.com/dgraph-io/dgraph/schema" "github.com/dgraph-io/dgraph/tok" @@ -284,7 +283,6 @@ func serveGRPC(l net.Listener, tlsCfg *tls.Config, wg *sync.WaitGroup) { s := grpc.NewServer(opt...) api.RegisterDgraphServer(s, &edgraph.Server{}) - api.RegisterDgraphAccessServer(s, &acl.AccessServer{}) hapi.RegisterHealthServer(s, health.NewServer()) err := s.Serve(l) glog.Errorf("GRPC listener canceled: %v\n", err) @@ -391,7 +389,7 @@ var shutdownCh chan struct{} func run() { bindall = Alpha.Conf.GetBool("bindall") - edgraph.SetConfiguration(edgraph.Options{ + edgraphOptions := edgraph.Options{ BadgerTables: Alpha.Conf.GetString("badger.tables"), BadgerVlog: Alpha.Conf.GetString("badger.vlog"), @@ -401,7 +399,7 @@ func run() { Nomutations: Alpha.Conf.GetBool("nomutations"), AuthToken: Alpha.Conf.GetString("auth_token"), AllottedMemory: Alpha.Conf.GetFloat64("lru_mb"), - }) + } secretFile := Alpha.Conf.GetString("hmac_secret_file") if secretFile != "" { @@ -410,12 +408,12 @@ func run() { glog.Fatalf("Unable to read HMAC secret from file: %v", secretFile) } - acl.SetAccessConfiguration(acl.AccessOptions{ - HmacSecret: hmacSecret, - JwtTtl: Alpha.Conf.GetDuration("jwt_ttl"), - }) + edgraphOptions.HmacSecret = hmacSecret + edgraphOptions.JwtTtl = Alpha.Conf.GetDuration("jwt_ttl") + glog.Info("HMAC secret loaded successfully.") } + edgraph.SetConfiguration(edgraphOptions) ips, err := parseIPsFromString(Alpha.Conf.GetString("whitelist")) x.Check(err) diff --git a/dgraph/cmd/live/run.go b/dgraph/cmd/live/run.go index 6011b3c8f91..1808c03eb63 100644 --- a/dgraph/cmd/live/run.go +++ b/dgraph/cmd/live/run.go @@ -46,11 +46,6 @@ import ( "github.com/spf13/cobra" ) -const ( - tlsLiveCert = "client.live.crt" - tlsLiveKey = "client.live.key" -) - type options struct { files string schemaFile string @@ -299,7 +294,7 @@ func run() error { ignoreIndexConflict: Live.Conf.GetBool("ignore_index_conflict"), authToken: Live.Conf.GetString("auth_token"), } - x.LoadTLSConfig(&tlsConf, Live.Conf, tlsLiveCert, tlsLiveKey) + x.LoadTLSConfig(&tlsConf, Live.Conf, x.TlsClientCert, x.TlsClientKey) tlsConf.ServerName = Live.Conf.GetString("tls_server_name") go http.ListenAndServe("localhost:6060", nil) diff --git a/edgraph/access_oss.go b/edgraph/access_oss.go new file mode 100644 index 00000000000..afed4fe7b85 --- /dev/null +++ b/edgraph/access_oss.go @@ -0,0 +1,16 @@ +// +build oss + +package edgraph + +import ( + "context" + "github.com/dgraph-io/dgo/protos/api" + "github.com/dgraph-io/dgraph/oss" + "github.com/golang/glog" +) + +func (s *Server) Login(ctx context.Context, + request *api.LogInRequest) (*api.LogInResponse, error) { + glog.Infof("Login failed: %s", oss.ErrNotSupported) + return &api.LogInResponse{}, nil +} diff --git a/edgraph/config.go b/edgraph/config.go index 13a83fd70fd..7d7096e78fe 100644 --- a/edgraph/config.go +++ b/edgraph/config.go @@ -19,6 +19,7 @@ package edgraph import ( "expvar" "path/filepath" + "time" "github.com/dgraph-io/dgraph/posting" "github.com/dgraph-io/dgraph/worker" @@ -34,6 +35,9 @@ type Options struct { AuthToken string AllottedMemory float64 + + HmacSecret []byte + JwtTtl time.Duration } var Config Options diff --git a/ee/acl/accessserver.go b/ee/acl/accessserver.go deleted file mode 100644 index 0a6f31e8461..00000000000 --- a/ee/acl/accessserver.go +++ /dev/null @@ -1,148 +0,0 @@ -package acl - -import ( - "context" - "fmt" - "time" - - "github.com/dgraph-io/dgo/protos/api" - "github.com/dgraph-io/dgraph/edgraph" - "github.com/dgraph-io/dgraph/ee/acl/cmd" - "github.com/golang/glog" - "go.opencensus.io/trace" - otrace "go.opencensus.io/trace" - "google.golang.org/grpc/peer" -) - -// Server implements protos.DgraphAccessServer -type AccessServer struct{} - -type AccessOptions struct { - HmacSecret []byte - JwtTtl time.Duration -} - -var accessConfig AccessOptions - -func SetAccessConfiguration(newConfig AccessOptions) { - accessConfig = newConfig -} - -func (accessServer *AccessServer) LogIn(ctx context.Context, - request *api.LogInRequest) (*api.LogInResponse, error) { - ctx, span := otrace.StartSpan(ctx, "accessserver.LogIn") - defer span.End() - - // record the client ip for this login request - clientPeer, ok := peer.FromContext(ctx) - if ok { - span.Annotate([]trace.Attribute{ - trace.StringAttribute("client_ip", clientPeer.Addr.String()), - }, "client ip for login") - } - - if err := validateLoginRequest(request); err != nil { - return nil, err - } - - resp := &api.LogInResponse{ - Context: &api.TxnContext{}, // context needed in order to set the jwt below - Code: api.AclResponseCode_UNAUTHENTICATED, - } - - user, err := queryUser(ctx, request.Userid, request.Password) - if err != nil { - glog.Warningf("Unable to login user with user id: %v", request.Userid) - return nil, err - } - if user == nil { - errMsg := fmt.Sprintf("User not found for user id %v", request.Userid) - glog.Warningf(errMsg) - return nil, fmt.Errorf(errMsg) - } - if !user.PasswordMatch { - errMsg := fmt.Sprintf("Password mismatch for user: %v", request.Userid) - glog.Warningf(errMsg) - return nil, fmt.Errorf(errMsg) - } - - jwt := &Jwt{ - Header: StdJwtHeader, - Payload: JwtPayload{ - Userid: request.Userid, - Groups: toJwtGroups(user.Groups), - // TODO add the token refresh mechanism - Exp: time.Now().Add(accessConfig.JwtTtl).Unix(), // set the jwt valid for 30 days - }, - } - - resp.Context.Jwt, err = jwt.EncodeToString(accessConfig.HmacSecret) - if err != nil { - glog.Errorf("Unable to encode jwt to string: %v", err) - return nil, err - } - - resp.Code = api.AclResponseCode_OK - return resp, nil -} - -func validateLoginRequest(request *api.LogInRequest) error { - if request == nil { - return fmt.Errorf("the request should not be nil") - } - if len(request.Userid) == 0 { - return fmt.Errorf("the userid should not be empty") - } - if len(request.Password) == 0 { - return fmt.Errorf("the password should not be empty") - } - return nil -} - -func queryUser(ctx context.Context, userid string, password string) (user *acl.User, err error) { - queryUid := ` - query search($userid: string, $password: string){ - user(func: eq(dgraph.xid, $userid)) { - uid - password_match: checkpwd(dgraph.password, $password) - dgraph.user.group { - uid - dgraph.xid - } - } - }` - - queryVars := make(map[string]string) - queryVars["$userid"] = userid - queryVars["$password"] = password - queryRequest := api.Request{ - Query: queryUid, - Vars: queryVars, - } - - queryResp, err := (&edgraph.Server{}).Query(ctx, &queryRequest) - if err != nil { - glog.Errorf("Error while query user with id %s: %v", userid, err) - return nil, err - } - user, err = acl.UnmarshallUser(queryResp, "user") - if err != nil { - return nil, err - } - return user, nil -} - -func toJwtGroups(groups []acl.Group) []JwtGroup { - if groups == nil { - // the user does not have any groups - return nil - } - - jwtGroups := make([]JwtGroup, len(groups)) - for _, g := range groups { - jwtGroups = append(jwtGroups, JwtGroup{ - Group: g.GroupID, - }) - } - return jwtGroups -} diff --git a/ee/acl/acl_test.go b/ee/acl/acl_test.go index e7e9dcc0ab9..7b15b9ba6cc 100644 --- a/ee/acl/acl_test.go +++ b/ee/acl/acl_test.go @@ -1,3 +1,15 @@ +// +build !oss + +/* + * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * + * Licensed under the Dgraph Community License (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt + */ + package acl import ( diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 092bd332781..e817a7e47cc 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -1,3 +1,15 @@ +// +build !oss + +/* + * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * + * Licensed under the Dgraph Community License (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt + */ + package acl import ( @@ -5,6 +17,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/dgraph-io/dgraph/ee/acl" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" @@ -92,7 +105,8 @@ func groupDel(dc *dgo.Dgraph) error { return nil } -func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string, fields []string) (group *Group, err error) { +func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string, + fields []string) (group *acl.Group, err error) { var queryBuilder bytes.Buffer // write query header queryBuilder.WriteString(` @@ -119,42 +133,19 @@ func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string, fields []stri glog.Errorf("Error while query group with id %s: %v", groupid, err) return nil, err } - group, err = UnmarshallGroup(queryResp, "group") + group, err = acl.UnmarshallGroup(queryResp, "group") if err != nil { return nil, err } return group, nil } -// Extract the first User pointed by the userKey in the query response -func UnmarshallGroup(queryResp *api.Response, groupKey string) (group *Group, err error) { - m := make(map[string][]Group) - - err = json.Unmarshal(queryResp.GetJson(), &m) - if err != nil { - glog.Errorf("Unable to unmarshal the query group response:%v", err) - return nil, err - } - groups := m[groupKey] - if len(groups) == 0 { - // the group does not exist - return nil, nil - } - - return &groups[0], nil -} type Acl struct { Predicate string `json:"predicate"` Perm int32 `json:"perm"` } -// parse the response and check existing of the uid -type Group struct { - Uid string `json:"uid"` - GroupID string `json:"dgraph.xid"` - Acls string `json:"dgraph.group.acl"` -} func chMod(dc *dgo.Dgraph) error { groupId := ChMod.Conf.GetString("group") diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go index 6d84f446b28..5e12e1cf223 100644 --- a/ee/acl/cmd/run.go +++ b/ee/acl/cmd/run.go @@ -1,3 +1,15 @@ +// +build !oss + +/* + * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * + * Licensed under the Dgraph Community License (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt + */ + package acl import ( @@ -30,11 +42,6 @@ var GroupDel x.SubCommand var UserMod x.SubCommand var ChMod x.SubCommand -const ( - tlsAclCert = "client.acl.crt" - tlsAclKey = "client.acl.key" -) - func init() { CmdAcl.Cmd = &cobra.Command{ Use: "acl", @@ -131,7 +138,7 @@ func initSubcommands() { } userModFlags := UserMod.Cmd.Flags() userModFlags.StringP("user", "u", "", "The user id to be changed") - userModFlags.StringP("groups", "G", "", "The groups to be set for the user") + userModFlags.StringP("groups", "g", "", "The groups to be set for the user") // the chmod command is used to change a group's permissions ChMod.Cmd = &cobra.Command{ @@ -142,9 +149,12 @@ func initSubcommands() { }, } chModFlags := ChMod.Cmd.Flags() - chModFlags.StringP("group", "g", "", "The group whose permission is to be changed") - chModFlags.StringP("pred", "p", "", "The predicates whose acls are to be changed") - chModFlags.IntP("perm", "P", 0, "The acl represented using an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") + chModFlags.StringP("group", "g", "", "The group whose permission " + + "is to be changed") + chModFlags.StringP("pred", "p", "", "The predicates whose acls" + + " are to be changed") + chModFlags.IntP("perm", "P", 0, "The acl represented using " + + "an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") } func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { @@ -157,12 +167,11 @@ func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { glog.Fatalf("The --dgraph option must be set in order to connect to dgraph") } - x.LoadTLSConfig(&tlsConf, CmdAcl.Conf, tlsAclCert, tlsAclKey) + x.LoadTLSConfig(&tlsConf, CmdAcl.Conf, x.TlsClientCert, x.TlsClientKey) tlsConf.ServerName = CmdAcl.Conf.GetString("tls_server_name") ds := strings.Split(opt.dgraph, ",") var clients []api.DgraphClient - var accessClients []api.DgraphAccessClient for _, d := range ds { conn, err := x.SetupConnection(d, !tlsConf.CertRequired, &tlsConf) x.Checkf(err, "While trying to setup connection to Dgraph alpha.") @@ -170,12 +179,8 @@ func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { dc := api.NewDgraphClient(conn) clients = append(clients, dc) - - dgraphAccess := api.NewDgraphAccessClient(conn) - accessClients = append(accessClients, dgraphAccess) } dgraphClient := dgo.NewDgraphClient(clients...) - dgraphClient.SetAccessClients(accessClients...) if err := f(dgraphClient); err != nil { glog.Errorf("Error while running transaction: %v", err) os.Exit(1) diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 2d56d7c2b40..7ff32b4a960 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -1,9 +1,21 @@ +// +build !oss + +/* + * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * + * Licensed under the Dgraph Community License (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt + */ + package acl import ( "context" - "encoding/json" "fmt" + "github.com/dgraph-io/dgraph/ee/acl" "strings" "github.com/dgraph-io/dgo" @@ -27,7 +39,7 @@ func userAdd(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - user, err := queryUser(txn, ctx, userid) + user, err := queryUser(ctx, txn, userid) if err != nil { return err } @@ -73,7 +85,7 @@ func userDel(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - user, err := queryUser(txn, ctx, userid) + user, err := queryUser(ctx, txn, userid) if err != nil { return err } @@ -124,15 +136,7 @@ func userLogin(dc *dgo.Dgraph) error { return nil } -type User struct { - Uid string `json:"uid"` - UserID string `json:"dgraph.xid"` - Password string `json:"dgraph.password"` - PasswordMatch bool `json:"password_match"` - Groups []Group `json:"dgraph.user.group"` -} - -func queryUser(txn *dgo.Txn, ctx context.Context, userid string) (user *User, err error) { +func queryUser(ctx context.Context, txn *dgo.Txn, userid string) (user *acl.User, err error) { query := ` query search($userid: string){ user(func: eq(dgraph.xid, $userid)) { @@ -151,29 +155,14 @@ func queryUser(txn *dgo.Txn, ctx context.Context, userid string) (user *User, er if err != nil { return nil, fmt.Errorf("Error while query user with id %s: %v", userid, err) } - user, err = UnmarshallUser(queryResp, "user") + user, err = acl.UnmarshallUser(queryResp, "user") if err != nil { return nil, err } return user, nil } -// Extract the first User pointed by the userKey in the query response -func UnmarshallUser(queryResp *api.Response, userKey string) (user *User, err error) { - m := make(map[string][]User) - err = json.Unmarshal(queryResp.GetJson(), &m) - if err != nil { - return nil, fmt.Errorf("Unable to unmarshal the query user response for user:%v", err) - } - users := m[userKey] - if len(users) == 0 { - // the user does not exist - return nil, nil - } - - return &users[0], nil -} func userMod(dc *dgo.Dgraph) error { userid := UserMod.Conf.GetString("user") @@ -186,7 +175,7 @@ func userMod(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - user, err := queryUser(txn, ctx, userid) + user, err := queryUser(ctx, txn, userid) if err != nil { return err } @@ -194,18 +183,19 @@ func userMod(dc *dgo.Dgraph) error { return fmt.Errorf("The user does not exist: %v", userid) } - targetGroupsMap := make(map[string]bool) + targetGroupsMap := make(map[string]struct{}) + var exists = struct{}{} if len(groups) > 0 { for _, g := range strings.Split(groups, ",") { - targetGroupsMap[g] = true + targetGroupsMap[g] = exists } } - existingGroupsMap := make(map[string]bool) + existingGroupsMap := make(map[string]struct{}) for _, g := range user.Groups { - existingGroupsMap[g.GroupID] = true + existingGroupsMap[g.GroupID] = exists } - newGroups, groupsToBeDeleted := calcDiffs(targetGroupsMap, existingGroupsMap) + newGroups, groupsToBeDeleted := x.CalcDiffs(targetGroupsMap, existingGroupsMap) mu := &api.Mutation{ CommitNow: true, @@ -215,7 +205,7 @@ func userMod(dc *dgo.Dgraph) error { for _, g := range newGroups { glog.Infof("Adding user %v to group %v", userid, g) - nquad, err := getUserModNQuad(txn, ctx, user.Uid, g) + nquad, err := getUserModNQuad(ctx, txn, user.Uid, g) if err != nil { return fmt.Errorf("error while getting the user mod nquad:%v", err) } @@ -225,7 +215,7 @@ func userMod(dc *dgo.Dgraph) error { for _, g := range groupsToBeDeleted { glog.Infof("Deleting user %v from group %v", userid, g) - nquad, err := getUserModNQuad(txn, ctx, user.Uid, g) + nquad, err := getUserModNQuad(ctx, txn, user.Uid, g) if err != nil { return fmt.Errorf("error while getting the user mod nquad:%v", err) } @@ -245,13 +235,14 @@ func userMod(dc *dgo.Dgraph) error { return nil } -func getUserModNQuad(txn *dgo.Txn, ctx context.Context, useruid string, groupid string) (*api.NQuad, error) { +func getUserModNQuad(ctx context.Context, txn *dgo.Txn, useruid string, + groupid string) (*api.NQuad, error) { group, err := queryGroup(txn, ctx, groupid, []string{"uid"}) if err != nil { return nil, err } if group == nil { - return nil, fmt.Errorf("The group does not exist:%v", groupid) + return nil, fmt.Errorf("the group does not exist:%v", groupid) } createUserGroupNQuads := &api.NQuad{ @@ -262,22 +253,3 @@ func getUserModNQuad(txn *dgo.Txn, ctx context.Context, useruid string, groupid return createUserGroupNQuads, nil } - -func calcDiffs(targetMap map[string]bool, existingMap map[string]bool) ([]string, []string) { - - newGroups := []string{} - groupsToBeDeleted := []string{} - - for g, _ := range targetMap { - if _, ok := existingMap[g]; !ok { - newGroups = append(newGroups, g) - } - } - for g, _ := range existingMap { - if _, ok := targetMap[g]; !ok { - groupsToBeDeleted = append(groupsToBeDeleted, g) - } - } - - return newGroups, groupsToBeDeleted -} diff --git a/ee/acl/jwt.go b/ee/acl/jwt.go index 1f2f0306c3e..d79cc50dd67 100644 --- a/ee/acl/jwt.go +++ b/ee/acl/jwt.go @@ -1,3 +1,15 @@ +// +build !oss + +/* + * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * + * Licensed under the Dgraph Community License (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt + */ + package acl import ( diff --git a/oss/errors.go b/oss/errors.go new file mode 100644 index 00000000000..3c447ce4e28 --- /dev/null +++ b/oss/errors.go @@ -0,0 +1,7 @@ +// +build oss + +package oss + +import "errors" + +var ErrNotSupported = errors.New("Feature available only in Dgraph Enterprise Edition.") \ No newline at end of file diff --git a/worker/backup_oss.go b/worker/backup_oss.go index c4abb9d8ece..1914ff84c93 100644 --- a/worker/backup_oss.go +++ b/worker/backup_oss.go @@ -19,23 +19,22 @@ package worker import ( - "errors" - + "github.com/dgraph-io/dgraph/oss" "github.com/dgraph-io/dgraph/protos/pb" "github.com/dgraph-io/dgraph/x" "github.com/golang/glog" "golang.org/x/net/context" ) -var errNotSupported = errors.New("Feature available only in Dgraph Enterprise Edition.") + // Backup implements the Worker interface. func (w *grpcWorker) Backup(ctx context.Context, req *pb.BackupRequest) (*pb.Status, error) { - glog.Infof("Backup failed: %s", errNotSupported) + glog.Infof("Backup failed: %s", oss.ErrNotSupported) return &pb.Status{}, nil } // BackupOverNetwork handles a request coming from an HTTP client. func BackupOverNetwork(pctx context.Context, target string) error { - return x.Errorf("Backup failed: %s", errNotSupported) + return x.Errorf("Backup failed: %s", oss.ErrNotSupported) } diff --git a/x/x.go b/x/x.go index 847091818ee..ef139e2563e 100644 --- a/x/x.go +++ b/x/x.go @@ -67,6 +67,9 @@ const ( // If the difference between AppliedUntil - TxnMarks.DoneUntil() is greater than this, we // start aborting old transactions. ForceAbortDifference = 5000 + + TlsClientCert = "client.crt" + TlsClientKey = "client.key" ) var ( @@ -441,3 +444,22 @@ func SetupConnection(host string, insecure bool, tlsConf *TLSHelperConfig) (*grp grpc.WithBlock(), grpc.WithTimeout(10*time.Second)) } + +func CalcDiffs(targetMap map[string]struct{}, existingMap map[string]struct{}) ([]string, + []string) { + var newGroups []string + var groupsToBeDeleted []string + + for g := range targetMap { + if _, ok := existingMap[g]; !ok { + newGroups = append(newGroups, g) + } + } + for g := range existingMap { + if _, ok := targetMap[g]; !ok { + groupsToBeDeleted = append(groupsToBeDeleted, g) + } + } + + return newGroups, groupsToBeDeleted +} \ No newline at end of file From 4f7b9dcacbc87b3056022b9243b95969d02db183 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 29 Nov 2018 13:38:00 -0800 Subject: [PATCH 14/30] Error handling and goimports --- dgraph/cmd/alpha/run.go | 4 ++-- ee/acl/cmd/groups.go | 5 +---- ee/acl/jwt.go | 8 ++++++-- x/x.go | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/dgraph/cmd/alpha/run.go b/dgraph/cmd/alpha/run.go index b546adf4b9f..5f9341ee7ef 100644 --- a/dgraph/cmd/alpha/run.go +++ b/dgraph/cmd/alpha/run.go @@ -408,8 +408,8 @@ func run() { glog.Fatalf("Unable to read HMAC secret from file: %v", secretFile) } - edgraphOptions.HmacSecret = hmacSecret - edgraphOptions.JwtTtl = Alpha.Conf.GetDuration("jwt_ttl") + edgraphOptions.HmacSecret = hmacSecret + edgraphOptions.JwtTtl = Alpha.Conf.GetDuration("jwt_ttl") glog.Info("HMAC secret loaded successfully.") } diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index e817a7e47cc..dee82b9faba 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -9,7 +9,6 @@ * * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt */ - package acl import ( @@ -17,10 +16,10 @@ import ( "context" "encoding/json" "fmt" - "github.com/dgraph-io/dgraph/ee/acl" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" + "github.com/dgraph-io/dgraph/ee/acl" "github.com/dgraph-io/dgraph/x" "github.com/golang/glog" ) @@ -140,13 +139,11 @@ func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string, return group, nil } - type Acl struct { Predicate string `json:"predicate"` Perm int32 `json:"perm"` } - func chMod(dc *dgo.Dgraph) error { groupId := ChMod.Conf.GetString("group") predicate := ChMod.Conf.GetString("pred") diff --git a/ee/acl/jwt.go b/ee/acl/jwt.go index d79cc50dd67..c196072b9aa 100644 --- a/ee/acl/jwt.go +++ b/ee/acl/jwt.go @@ -111,8 +111,12 @@ func (jwt *Jwt) DecodeString(input string, checkSignature bool, key []byte) erro } mac := hmac.New(sha256.New, key) - mac.Write(header) - mac.Write(payload) + if _, err := mac.Write(header); err != nil { + return fmt.Errorf("Error while writing header to construct signature: %v", err) + } + if _, err := mac.Write(payload); err != nil { + return fmt.Errorf("Error while writing payload to construct signature: %v", err) + } expectedSignature := mac.Sum(nil) if !hmac.Equal(signature, expectedSignature) { return fmt.Errorf("Signature mismatch") diff --git a/x/x.go b/x/x.go index ef139e2563e..fb7d170a4fa 100644 --- a/x/x.go +++ b/x/x.go @@ -462,4 +462,4 @@ func CalcDiffs(targetMap map[string]struct{}, existingMap map[string]struct{}) ( } return newGroups, groupsToBeDeleted -} \ No newline at end of file +} From 303a74963ac77e1a00036a064d4aa676b060e89e Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 29 Nov 2018 13:40:59 -0800 Subject: [PATCH 15/30] goimports --- ee/acl/cmd/run.go | 6 +++--- ee/acl/cmd/users.go | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go index 5e12e1cf223..a1eec3f25f4 100644 --- a/ee/acl/cmd/run.go +++ b/ee/acl/cmd/run.go @@ -149,11 +149,11 @@ func initSubcommands() { }, } chModFlags := ChMod.Cmd.Flags() - chModFlags.StringP("group", "g", "", "The group whose permission " + + chModFlags.StringP("group", "g", "", "The group whose permission "+ "is to be changed") - chModFlags.StringP("pred", "p", "", "The predicates whose acls" + + chModFlags.StringP("pred", "p", "", "The predicates whose acls"+ " are to be changed") - chModFlags.IntP("perm", "P", 0, "The acl represented using " + + chModFlags.IntP("perm", "P", 0, "The acl represented using "+ "an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") } diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 7ff32b4a960..377890faf59 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -15,11 +15,11 @@ package acl import ( "context" "fmt" - "github.com/dgraph-io/dgraph/ee/acl" "strings" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" + "github.com/dgraph-io/dgraph/ee/acl" "github.com/dgraph-io/dgraph/x" "github.com/golang/glog" ) @@ -162,8 +162,6 @@ func queryUser(ctx context.Context, txn *dgo.Txn, userid string) (user *acl.User return user, nil } - - func userMod(dc *dgo.Dgraph) error { userid := UserMod.Conf.GetString("user") groups := UserMod.Conf.GetString("groups") From b65a2af4f8e29c7c3b72e85e898d710d83a4f056 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 29 Nov 2018 14:34:18 -0800 Subject: [PATCH 16/30] Adding the utils and access_ee files --- edgraph/access_ee.go | 117 +++++++++++++++++++++++++++++++++++++++++++ ee/acl/utils.go | 101 +++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 edgraph/access_ee.go create mode 100644 ee/acl/utils.go diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go new file mode 100644 index 00000000000..0a83865c746 --- /dev/null +++ b/edgraph/access_ee.go @@ -0,0 +1,117 @@ +// +build !oss + +/* + * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * + * Licensed under the Dgraph Community License (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt + */ + +package edgraph + +import ( + "context" + "fmt" + "github.com/dgraph-io/dgo/protos/api" + "github.com/dgraph-io/dgraph/ee/acl" + "github.com/golang/glog" + "google.golang.org/grpc/peer" + "time" + + otrace "go.opencensus.io/trace" +) + +func (s *Server) Login(ctx context.Context, + request *api.LogInRequest) (*api.LogInResponse, error) { + ctx, span := otrace.StartSpan(ctx, "server.LogIn") + defer span.End() + + // record the client ip for this login request + clientPeer, ok := peer.FromContext(ctx) + if ok { + span.Annotate([]otrace.Attribute{ + otrace.StringAttribute("client_ip", clientPeer.Addr.String()), + }, "client ip for login") + } + + if err := acl.ValidateLoginRequest(request); err != nil { + return nil, err + } + + resp := &api.LogInResponse{ + Context: &api.TxnContext{}, // context needed in order to set the jwt below + Code: api.AclResponseCode_UNAUTHENTICATED, + } + + user, err := s.queryUser(ctx, request.Userid, request.Password) + if err != nil { + glog.Warningf("Unable to login user with user id: %v", request.Userid) + return nil, err + } + if user == nil { + errMsg := fmt.Sprintf("User not found for user id %v", request.Userid) + glog.Warningf(errMsg) + return nil, fmt.Errorf(errMsg) + } + if !user.PasswordMatch { + errMsg := fmt.Sprintf("Password mismatch for user: %v", request.Userid) + glog.Warningf(errMsg) + return nil, fmt.Errorf(errMsg) + } + + jwt := &acl.Jwt{ + Header: acl.StdJwtHeader, + Payload: acl.JwtPayload{ + Userid: request.Userid, + Groups: acl.ToJwtGroups(user.Groups), + // TODO add the token refresh mechanism + Exp: time.Now().Add(Config.JwtTtl).Unix(), // set the jwt valid for 30 days + }, + } + + resp.Context.Jwt, err = jwt.EncodeToString(Config.HmacSecret) + if err != nil { + glog.Errorf("Unable to encode jwt to string: %v", err) + return nil, err + } + + resp.Code = api.AclResponseCode_OK + return resp, nil +} + +const queryUser = ` + query search($userid: string, $password: string){ + user(func: eq(dgraph.xid, $userid)) { + uid + password_match: checkpwd(dgraph.password, $password) + dgraph.user.group { + uid + dgraph.xid + } + } + }` + +func (s *Server) queryUser(ctx context.Context, userid string, password string) (user *acl.User, + err error) { + queryVars := make(map[string]string) + queryVars["$userid"] = userid + queryVars["$password"] = password + queryRequest := api.Request{ + Query: queryUser, + Vars: queryVars, + } + + queryResp, err := s.Query(ctx, &queryRequest) + if err != nil { + glog.Errorf("Error while query user with id %s: %v", userid, err) + return nil, err + } + user, err = acl.UnmarshallUser(queryResp, "user") + if err != nil { + return nil, err + } + return user, nil +} \ No newline at end of file diff --git a/ee/acl/utils.go b/ee/acl/utils.go new file mode 100644 index 00000000000..dba157667e6 --- /dev/null +++ b/ee/acl/utils.go @@ -0,0 +1,101 @@ +// +build !oss + +/* + * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * + * Licensed under the Dgraph Community License (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt + */ + +package acl + +import ( + "encoding/json" + "fmt" + "github.com/dgraph-io/dgo/protos/api" + "github.com/golang/glog" +) + +func ValidateLoginRequest(request *api.LogInRequest) error { + if request == nil { + return fmt.Errorf("the request should not be nil") + } + if len(request.Userid) == 0 { + return fmt.Errorf("the userid should not be empty") + } + if len(request.Password) == 0 { + return fmt.Errorf("the password should not be empty") + } + return nil +} + + +func ToJwtGroups(groups []Group) []JwtGroup { + if groups == nil { + // the user does not have any groups + return nil + } + + jwtGroups := make([]JwtGroup, len(groups)) + for _, g := range groups { + jwtGroups = append(jwtGroups, JwtGroup{ + Group: g.GroupID, + }) + } + return jwtGroups +} + + +type User struct { + Uid string `json:"uid"` + UserID string `json:"dgraph.xid"` + Password string `json:"dgraph.password"` + PasswordMatch bool `json:"password_match"` + Groups []Group `json:"dgraph.user.group"` +} + +// Extract the first User pointed by the userKey in the query response +func UnmarshallUser(queryResp *api.Response, userKey string) (user *User, err error) { + m := make(map[string][]User) + + err = json.Unmarshal(queryResp.GetJson(), &m) + if err != nil { + return nil, fmt.Errorf("Unable to unmarshal the query user response for user:%v", err) + } + users := m[userKey] + if len(users) == 0 { + // the user does not exist + return nil, nil + } + + return &users[0], nil +} + +// parse the response and check existing of the uid +type Group struct { + Uid string `json:"uid"` + GroupID string `json:"dgraph.xid"` + Acls string `json:"dgraph.group.acl"` +} + + +// Extract the first User pointed by the userKey in the query response +func UnmarshallGroup(queryResp *api.Response, groupKey string) (group *Group, err error) { + m := make(map[string][]Group) + + err = json.Unmarshal(queryResp.GetJson(), &m) + if err != nil { + glog.Errorf("Unable to unmarshal the query group response:%v", err) + return nil, err + } + groups := m[groupKey] + if len(groups) == 0 { + // the group does not exist + return nil, nil + } + + return &groups[0], nil +} \ No newline at end of file From e7a7db17020acb8d0d9960ae3833f36075e004f5 Mon Sep 17 00:00:00 2001 From: Manish R Jain Date: Thu, 29 Nov 2018 14:34:55 -0800 Subject: [PATCH 17/30] Manish review --- dgraph/cmd/alpha/run.go | 8 +- worker/backup_oss.go => edgraph/access.go | 22 ++-- edgraph/access_oss.go | 16 --- ee/acl/acl_test.go | 29 ++--- ee/acl/cmd/groups.go | 48 +++----- ee/acl/cmd/run.go | 2 +- ee/acl/cmd/users.go | 2 +- ee/acl/jwt.go | 51 ++++---- ee/backup/backup.go | 2 +- ee/backup/file_handler.go | 2 +- ee/backup/s3_handler.go | 2 +- ee/backup/writer.go | 2 +- oss/errors.go | 7 -- worker/backup.go | 116 +----------------- worker/backup_ee.go | 137 ++++++++++++++++++++++ x/x.go | 41 +++---- 16 files changed, 235 insertions(+), 252 deletions(-) rename worker/backup_oss.go => edgraph/access.go (57%) delete mode 100644 edgraph/access_oss.go delete mode 100644 oss/errors.go create mode 100644 worker/backup_ee.go diff --git a/dgraph/cmd/alpha/run.go b/dgraph/cmd/alpha/run.go index 5f9341ee7ef..839e3d0bb8f 100644 --- a/dgraph/cmd/alpha/run.go +++ b/dgraph/cmd/alpha/run.go @@ -389,7 +389,7 @@ var shutdownCh chan struct{} func run() { bindall = Alpha.Conf.GetBool("bindall") - edgraphOptions := edgraph.Options{ + opts := edgraph.Options{ BadgerTables: Alpha.Conf.GetString("badger.tables"), BadgerVlog: Alpha.Conf.GetString("badger.vlog"), @@ -408,12 +408,12 @@ func run() { glog.Fatalf("Unable to read HMAC secret from file: %v", secretFile) } - edgraphOptions.HmacSecret = hmacSecret - edgraphOptions.JwtTtl = Alpha.Conf.GetDuration("jwt_ttl") + opts.HmacSecret = hmacSecret + opts.JwtTtl = Alpha.Conf.GetDuration("jwt_ttl") glog.Info("HMAC secret loaded successfully.") } - edgraph.SetConfiguration(edgraphOptions) + edgraph.SetConfiguration(opts) ips, err := parseIPsFromString(Alpha.Conf.GetString("whitelist")) x.Check(err) diff --git a/worker/backup_oss.go b/edgraph/access.go similarity index 57% rename from worker/backup_oss.go rename to edgraph/access.go index 1914ff84c93..21bcb7a9622 100644 --- a/worker/backup_oss.go +++ b/edgraph/access.go @@ -16,25 +16,19 @@ * limitations under the License. */ -package worker +package edgraph import ( - "github.com/dgraph-io/dgraph/oss" - "github.com/dgraph-io/dgraph/protos/pb" + "context" + + "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/x" "github.com/golang/glog" - "golang.org/x/net/context" ) +func (s *Server) Login(ctx context.Context, + request *api.LogInRequest) (*api.LogInResponse, error) { - -// Backup implements the Worker interface. -func (w *grpcWorker) Backup(ctx context.Context, req *pb.BackupRequest) (*pb.Status, error) { - glog.Infof("Backup failed: %s", oss.ErrNotSupported) - return &pb.Status{}, nil -} - -// BackupOverNetwork handles a request coming from an HTTP client. -func BackupOverNetwork(pctx context.Context, target string) error { - return x.Errorf("Backup failed: %s", oss.ErrNotSupported) + glog.Warningf("Login failed: %s", x.ErrNotSupported) + return &api.LogInResponse{}, x.ErrNotSupported } diff --git a/edgraph/access_oss.go b/edgraph/access_oss.go deleted file mode 100644 index afed4fe7b85..00000000000 --- a/edgraph/access_oss.go +++ /dev/null @@ -1,16 +0,0 @@ -// +build oss - -package edgraph - -import ( - "context" - "github.com/dgraph-io/dgo/protos/api" - "github.com/dgraph-io/dgraph/oss" - "github.com/golang/glog" -) - -func (s *Server) Login(ctx context.Context, - request *api.LogInRequest) (*api.LogInResponse, error) { - glog.Infof("Login failed: %s", oss.ErrNotSupported) - return &api.LogInResponse{}, nil -} diff --git a/ee/acl/acl_test.go b/ee/acl/acl_test.go index 7b15b9ba6cc..005e974e2e5 100644 --- a/ee/acl/acl_test.go +++ b/ee/acl/acl_test.go @@ -1,7 +1,7 @@ // +build !oss /* - * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * Copyright 2018 Dgraph Labs, Inc. and Contributors * * Licensed under the Dgraph Community License (the "License"); you * may not use this file except in compliance with the License. You @@ -63,21 +63,22 @@ func CreateAndDeleteUsers(t *testing.T) { t.Logf("Got output when creating user:%v", createUserOutput3) } -func LogIn(t *testing.T) { - // delete and recreate the user to ensure a clean state - /* - deleteUserCmd := exec.Command("dgraph", "acl", "userdel", "-d", dgraphEndpoint, "-u", "lucas") - deleteUserOutput := checkOutput(t, deleteUserCmd, false) - createUserCmd := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", "lucas", - "-p", "haha") - createUserOutput := checkOutput(t, createUserCmd, false) - */ +// TODO(gitlw): Finish this later. +// func LogIn(t *testing.T) { +// delete and recreate the user to ensure a clean state +/* + deleteUserCmd := exec.Command("dgraph", "acl", "userdel", "-d", dgraphEndpoint, "-u", "lucas") + deleteUserOutput := checkOutput(t, deleteUserCmd, false) + createUserCmd := exec.Command("dgraph", "acl", "useradd", "-d", dgraphEndpoint, "-u", "lucas", + "-p", "haha") + createUserOutput := checkOutput(t, createUserCmd, false) +*/ - // now try to login with the wrong password +// now try to login with the wrong password - //loginWithWrongPassword(t, ctx, adminClient) - //loginWithCorrectPassword(t, ctx, adminClient) -} +//loginWithWrongPassword(t, ctx, adminClient) +//loginWithCorrectPassword(t, ctx, adminClient) +// } /* func loginWithCorrectPassword(t *testing.T, ctx context.Context, diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index dee82b9faba..5901ffc26f2 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -1,7 +1,7 @@ // +build !oss /* - * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * Copyright 2018 Dgraph Labs, Inc. and Contributors * * Licensed under the Dgraph Community License (the "License"); you * may not use this file except in compliance with the License. You @@ -12,10 +12,11 @@ package acl import ( - "bytes" "context" "encoding/json" "fmt" + "strings" + "time" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" @@ -34,7 +35,7 @@ func groupAdd(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - group, err := queryGroup(txn, ctx, groupId, []string{"uid"}) + group, err := queryGroup(ctx, txn, groupId, "uid") if err != nil { return err } @@ -71,15 +72,16 @@ func groupDel(dc *dgo.Dgraph) error { return fmt.Errorf("the group id should not be empty") } - ctx := context.Background() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + txn := dc.NewTxn() defer txn.Discard(ctx) - group, err := queryGroup(txn, ctx, groupId, []string{"uid"}) + group, err := queryGroup(ctx, txn, groupId, "uid") if err != nil { return err } - if group == nil || len(group.Uid) == 0 { return fmt.Errorf("Unable to delete group because it does not exist: %v", groupId) } @@ -89,14 +91,13 @@ func groupDel(dc *dgo.Dgraph) error { Subject: group.Uid, Predicate: x.Star, ObjectValue: &api.Value{Val: &api.Value_DefaultVal{DefaultVal: x.Star}}, - }} + }, + } mu := &api.Mutation{ CommitNow: true, Del: deleteGroupNQuads, } - - _, err = txn.Mutate(ctx, mu) - if err != nil { + if _, err := txn.Mutate(ctx, mu); err != nil { return fmt.Errorf("Unable to delete group: %v", err) } @@ -104,30 +105,17 @@ func groupDel(dc *dgo.Dgraph) error { return nil } -func queryGroup(txn *dgo.Txn, ctx context.Context, groupid string, - fields []string) (group *acl.Group, err error) { - var queryBuilder bytes.Buffer - // write query header - queryBuilder.WriteString(` - query search($groupid: string){ - group(func: eq(dgraph.xid, $groupid)) { - `) - - for _, f := range fields { - queryBuilder.WriteString(f) - queryBuilder.WriteString("\n") - } - // write query footer - queryBuilder.WriteString(` - } - }`) +func queryGroup(ctx context.Context, txn *dgo.Txn, groupid string, + fields ...string) (group *acl.Group, err error) { - query := queryBuilder.String() + // write query header + query := fmt.Sprintf(`query search($groupid: string){ + group(func: eq(dgraph.xid, $groupid)) { %s }}`, strings.Join(fields, ", ")) queryVars := make(map[string]string) queryVars["$groupid"] = groupid - queryResp, err := txn.QueryWithVars(ctx, query, queryVars) + queryResp, err := txn.QueryWithVars(context.Background(), query, queryVars) if err != nil { glog.Errorf("Error while query group with id %s: %v", groupid, err) return nil, err @@ -159,7 +147,7 @@ func chMod(dc *dgo.Dgraph) error { txn := dc.NewTxn() defer txn.Discard(ctx) - group, err := queryGroup(txn, ctx, groupId, []string{"uid", "dgraph.group.acl"}) + group, err := queryGroup(txn, ctx, groupId, "uid", "dgraph.group.acl") if err != nil { return err } diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go index a1eec3f25f4..3b6f89b7c5b 100644 --- a/ee/acl/cmd/run.go +++ b/ee/acl/cmd/run.go @@ -1,7 +1,7 @@ // +build !oss /* - * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * Copyright 2018 Dgraph Labs, Inc. and Contributors * * Licensed under the Dgraph Community License (the "License"); you * may not use this file except in compliance with the License. You diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 377890faf59..e6a42104427 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -1,7 +1,7 @@ // +build !oss /* - * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * Copyright 2018 Dgraph Labs, Inc. and Contributors * * Licensed under the Dgraph Community License (the "License"); you * may not use this file except in compliance with the License. You diff --git a/ee/acl/jwt.go b/ee/acl/jwt.go index c196072b9aa..a2b8370d45e 100644 --- a/ee/acl/jwt.go +++ b/ee/acl/jwt.go @@ -1,7 +1,7 @@ // +build !oss /* - * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * Copyright 2018 Dgraph Labs, Inc. and Contributors * * Licensed under the Dgraph Community License (the "License"); you * may not use this file except in compliance with the License. You @@ -82,14 +82,14 @@ func (jwt *Jwt) EncodeToString(key []byte) (string, error) { // Decode the input string into the current Jwt struct, and also verify // that the signature in the input is valid using the key if checkSignature is true -func (jwt *Jwt) DecodeString(input string, checkSignature bool, key []byte) error { - if len(input) == 0 { - return fmt.Errorf("The input jwt should not be empty") - } +func (jwt *Jwt) DecodeString(input string, key []byte) error { components := strings.Split(input, ".") if len(components) != 3 { return fmt.Errorf("Input is not in format xxx.yyy.zzz") } + if len(key) == 0 { + return fmt.Errorf("The key should not be empty") + } header, err := base64.StdEncoding.DecodeString(components[0]) if err != nil { @@ -99,36 +99,27 @@ func (jwt *Jwt) DecodeString(input string, checkSignature bool, key []byte) erro if err != nil { return fmt.Errorf("Unable to base64 decode the payload: %v", components[1]) } + signature, err := base64.StdEncoding.DecodeString(components[2]) + if err != nil { + return fmt.Errorf("Unable to base64 decode the signature: %v", components[2]) + } - if checkSignature { - if len(key) == 0 { - return fmt.Errorf("The key should not be empty") - } - - signature, err := base64.StdEncoding.DecodeString(components[2]) - if err != nil { - return fmt.Errorf("Unable to base64 decode the signature: %v", components[2]) - } - - mac := hmac.New(sha256.New, key) - if _, err := mac.Write(header); err != nil { - return fmt.Errorf("Error while writing header to construct signature: %v", err) - } - if _, err := mac.Write(payload); err != nil { - return fmt.Errorf("Error while writing payload to construct signature: %v", err) - } - expectedSignature := mac.Sum(nil) - if !hmac.Equal(signature, expectedSignature) { - return fmt.Errorf("Signature mismatch") - } + mac := hmac.New(sha256.New, key) + if _, err := mac.Write(header); err != nil { + return fmt.Errorf("Error while writing header to construct signature: %v", err) + } + if _, err := mac.Write(payload); err != nil { + return fmt.Errorf("Error while writing payload to construct signature: %v", err) + } + expectedSignature := mac.Sum(nil) + if !hmac.Equal(signature, expectedSignature) { + return fmt.Errorf("JWT signature mismatch") } - err = json.Unmarshal(header, &jwt.Header) - if err != nil { + if err = json.Unmarshal(header, &jwt.Header); err != nil { return err } - err = json.Unmarshal(payload, &jwt.Payload) - if err != nil { + if err = json.Unmarshal(payload, &jwt.Payload); err != nil { return err } return nil diff --git a/ee/backup/backup.go b/ee/backup/backup.go index 8e471f958dd..f2c2697a11d 100644 --- a/ee/backup/backup.go +++ b/ee/backup/backup.go @@ -1,7 +1,7 @@ // +build !oss /* - * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * Copyright 2018 Dgraph Labs, Inc. and Contributors * * Licensed under the Dgraph Community License (the "License"); you * may not use this file except in compliance with the License. You diff --git a/ee/backup/file_handler.go b/ee/backup/file_handler.go index 54e2dce6bc3..c3dadb9255b 100644 --- a/ee/backup/file_handler.go +++ b/ee/backup/file_handler.go @@ -1,7 +1,7 @@ // +build !oss /* - * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * Copyright 2018 Dgraph Labs, Inc. and Contributors * * Licensed under the Dgraph Community License (the "License"); you * may not use this file except in compliance with the License. You diff --git a/ee/backup/s3_handler.go b/ee/backup/s3_handler.go index 32d5946039e..02d3bd1b422 100644 --- a/ee/backup/s3_handler.go +++ b/ee/backup/s3_handler.go @@ -1,7 +1,7 @@ // +build !oss /* - * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * Copyright 2018 Dgraph Labs, Inc. and Contributors * * Licensed under the Dgraph Community License (the "License"); you * may not use this file except in compliance with the License. You diff --git a/ee/backup/writer.go b/ee/backup/writer.go index ed5262047df..5e3c47d88cd 100644 --- a/ee/backup/writer.go +++ b/ee/backup/writer.go @@ -1,7 +1,7 @@ // +build !oss /* - * Copyright 2018 Dgraph Labs, Inc. All rights reserved. + * Copyright 2018 Dgraph Labs, Inc. and Contributors * * Licensed under the Dgraph Community License (the "License"); you * may not use this file except in compliance with the License. You diff --git a/oss/errors.go b/oss/errors.go deleted file mode 100644 index 3c447ce4e28..00000000000 --- a/oss/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -// +build oss - -package oss - -import "errors" - -var ErrNotSupported = errors.New("Feature available only in Dgraph Enterprise Edition.") \ No newline at end of file diff --git a/worker/backup.go b/worker/backup.go index 9fc2ac749d9..10f073e7f6a 100644 --- a/worker/backup.go +++ b/worker/backup.go @@ -1,4 +1,4 @@ -// +build !oss +// +build oss /* * Copyright 2018 Dgraph Labs, Inc. and Contributors @@ -19,125 +19,19 @@ package worker import ( - "time" - - "github.com/dgraph-io/dgraph/ee/backup" - "github.com/dgraph-io/dgraph/posting" "github.com/dgraph-io/dgraph/protos/pb" "github.com/dgraph-io/dgraph/x" - "github.com/golang/glog" "golang.org/x/net/context" ) -func backupProcess(ctx context.Context, req *pb.BackupRequest) error { - glog.Infof("Backup request: group %d at %d", req.GroupId, req.ReadTs) - if err := ctx.Err(); err != nil { - glog.Errorf("Context error during backup: %v\n", err) - return err - } - // sanity, make sure this is our group. - if groups().groupId() != req.GroupId { - return x.Errorf("Backup request group mismatch. Mine: %d. Requested: %d\n", - groups().groupId(), req.GroupId) - } - // wait for this node to catch-up. - if err := posting.Oracle().WaitForTs(ctx, req.ReadTs); err != nil { - return err - } - // create backup request and process it. - br := &backup.Request{DB: pstore, Backup: req} - // calculate estimated upload size - for _, t := range groups().tablets { - if t.GroupId == req.GroupId { - br.Sizex += uint64(float64(t.Space) * 1.2) - } - } - return br.Process(ctx) -} - -// Backup handles a request coming from another node. +// Backup implements the Worker interface. func (w *grpcWorker) Backup(ctx context.Context, req *pb.BackupRequest) (*pb.Status, error) { - var resp pb.Status - glog.V(2).Infof("Received backup request via Grpc: %+v", req) - if err := backupProcess(ctx, req); err != nil { - resp.Code = -1 - resp.Msg = err.Error() - return &resp, err - } - return &resp, nil -} - -func backupGroup(ctx context.Context, in pb.BackupRequest) error { - glog.V(2).Infof("Sending backup request: %+v\n", in) - // this node is part of the group, process backup. - if groups().groupId() == in.GroupId { - return backupProcess(ctx, &in) - } - - // send request to any node in the group. - pl := groups().AnyServer(in.GroupId) - if pl == nil { - return x.Errorf("Couldn't find a server in group %d", in.GroupId) - } - status, err := pb.NewWorkerClient(pl.Get()).Backup(ctx, &in) - if err != nil { - glog.Errorf("Backup error group %d: %s", in.GroupId, err) - return err - } - if status.Code != 0 { - err := x.Errorf("Backup error group %d: %s", in.GroupId, status.Msg) - glog.Errorln(err) - return err - } - glog.V(2).Infof("Backup request to gid=%d. OK\n", in.GroupId) - return nil + glog.Warningf("Backup failed: %v", x.ErrNotSupported) + return &pb.Status{}, x.ErrNotSupported } // BackupOverNetwork handles a request coming from an HTTP client. func BackupOverNetwork(pctx context.Context, target string) error { - ctx, cancel := context.WithCancel(pctx) - defer cancel() - - // Check that this node can accept requests. - if err := x.HealthCheck(); err != nil { - glog.Errorf("Backup canceled, not ready to accept requests: %s", err) - return err - } - - // Get ReadTs from zero and wait for stream to catch up. - ts, err := Timestamps(ctx, &pb.Num{ReadOnly: true}) - if err != nil { - glog.Errorf("Unable to retrieve readonly timestamp for backup: %s", err) - return err - } - - gids := groups().KnownGroups() - req := pb.BackupRequest{ - ReadTs: ts.ReadOnly, - Target: target, - UnixTs: time.Now().UTC().Format("20060102.1504"), - } - glog.Infof("Created backup request: %+v. Groups=%v\n", req, gids) - - // This will dispatch the request to all groups and wait for their response. - // If we receive any failures, we cancel the process. - errCh := make(chan error, 1) - for _, gid := range gids { - req.GroupId = gid - go func() { - errCh <- backupGroup(ctx, req) - }() - } - - for i := 0; i < len(gids); i++ { - err := <-errCh - if err != nil { - glog.Errorf("Error received during backup: %v", err) - return err - } - } - req.GroupId = 0 - glog.Infof("Backup for req: %+v. OK.\n", req) - return nil + return x.ErrNotSupported } diff --git a/worker/backup_ee.go b/worker/backup_ee.go new file mode 100644 index 00000000000..bb5e1a78f9f --- /dev/null +++ b/worker/backup_ee.go @@ -0,0 +1,137 @@ +// +build !oss + +/* + * Copyright 2018 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Dgraph Community License (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt + */ + +package worker + +import ( + "time" + + "github.com/dgraph-io/dgraph/ee/backup" + "github.com/dgraph-io/dgraph/posting" + "github.com/dgraph-io/dgraph/protos/pb" + "github.com/dgraph-io/dgraph/x" + + "github.com/golang/glog" + "golang.org/x/net/context" +) + +func backupProcess(ctx context.Context, req *pb.BackupRequest) error { + glog.Infof("Backup request: group %d at %d", req.GroupId, req.ReadTs) + if err := ctx.Err(); err != nil { + glog.Errorf("Context error during backup: %v\n", err) + return err + } + // sanity, make sure this is our group. + if groups().groupId() != req.GroupId { + return x.Errorf("Backup request group mismatch. Mine: %d. Requested: %d\n", + groups().groupId(), req.GroupId) + } + // wait for this node to catch-up. + if err := posting.Oracle().WaitForTs(ctx, req.ReadTs); err != nil { + return err + } + // create backup request and process it. + br := &backup.Request{DB: pstore, Backup: req} + // calculate estimated upload size + for _, t := range groups().tablets { + if t.GroupId == req.GroupId { + br.Sizex += uint64(float64(t.Space) * 1.2) + } + } + return br.Process(ctx) +} + +// Backup handles a request coming from another node. +func (w *grpcWorker) Backup(ctx context.Context, req *pb.BackupRequest) (*pb.Status, error) { + var resp pb.Status + glog.V(2).Infof("Received backup request via Grpc: %+v", req) + if err := backupProcess(ctx, req); err != nil { + resp.Code = -1 + resp.Msg = err.Error() + return &resp, err + } + return &resp, nil +} + +func backupGroup(ctx context.Context, in pb.BackupRequest) error { + glog.V(2).Infof("Sending backup request: %+v\n", in) + // this node is part of the group, process backup. + if groups().groupId() == in.GroupId { + return backupProcess(ctx, &in) + } + + // send request to any node in the group. + pl := groups().AnyServer(in.GroupId) + if pl == nil { + return x.Errorf("Couldn't find a server in group %d", in.GroupId) + } + status, err := pb.NewWorkerClient(pl.Get()).Backup(ctx, &in) + if err != nil { + glog.Errorf("Backup error group %d: %s", in.GroupId, err) + return err + } + if status.Code != 0 { + err := x.Errorf("Backup error group %d: %s", in.GroupId, status.Msg) + glog.Errorln(err) + return err + } + glog.V(2).Infof("Backup request to gid=%d. OK\n", in.GroupId) + return nil +} + +// BackupOverNetwork handles a request coming from an HTTP client. +func BackupOverNetwork(pctx context.Context, target string) error { + ctx, cancel := context.WithCancel(pctx) + defer cancel() + + // Check that this node can accept requests. + if err := x.HealthCheck(); err != nil { + glog.Errorf("Backup canceled, not ready to accept requests: %s", err) + return err + } + + // Get ReadTs from zero and wait for stream to catch up. + ts, err := Timestamps(ctx, &pb.Num{ReadOnly: true}) + if err != nil { + glog.Errorf("Unable to retrieve readonly timestamp for backup: %s", err) + return err + } + + gids := groups().KnownGroups() + req := pb.BackupRequest{ + ReadTs: ts.ReadOnly, + Target: target, + UnixTs: time.Now().UTC().Format("20060102.1504"), + } + glog.Infof("Created backup request: %+v. Groups=%v\n", req, gids) + + // This will dispatch the request to all groups and wait for their response. + // If we receive any failures, we cancel the process. + errCh := make(chan error, 1) + for _, gid := range gids { + req.GroupId = gid + go func() { + errCh <- backupGroup(ctx, req) + }() + } + + for i := 0; i < len(gids); i++ { + err := <-errCh + if err != nil { + glog.Errorf("Error received during backup: %v", err) + return err + } + } + req.GroupId = 0 + glog.Infof("Backup for req: %+v. OK.\n", req) + return nil +} diff --git a/x/x.go b/x/x.go index fb7d170a4fa..4fe090fdadf 100644 --- a/x/x.go +++ b/x/x.go @@ -35,6 +35,10 @@ import ( ) // Error constants representing different types of errors. +var ( + ErrNotSupported = fmt.Errorf("Feature available only in Dgraph Enterprise Edition.") +) + const ( Success = "Success" ErrorUnauthorized = "ErrorUnauthorized" @@ -47,7 +51,8 @@ const ( ErrorNoPermission = "ErrorNoPermission" ErrorInvalidMutation = "ErrorInvalidMutation" ErrorServiceUnavailable = "ErrorServiceUnavailable" - ValidHostnameRegex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$" + + ValidHostnameRegex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$" // When changing this value also remember to change in in client/client.go:DeleteEdges. Star = "_STAR_ALL" @@ -418,31 +423,27 @@ func DivideAndRule(num int) (numGo, width int) { return } -func SetupConnection(host string, insecure bool, tlsConf *TLSHelperConfig) (*grpc.ClientConn, - error) { - if insecure { - return grpc.Dial(host, - grpc.WithDefaultCallOptions( - grpc.MaxCallRecvMsgSize(GrpcMaxSize), - grpc.MaxCallSendMsgSize(GrpcMaxSize)), - grpc.WithInsecure(), - grpc.WithBlock(), - grpc.WithTimeout(10*time.Second)) - } - - tlsConf.ConfigType = TLSClientConfig - tlsCfg, _, err := GenerateTLSConfig(*tlsConf) - if err != nil { - return nil, err - } +func SetupConnection(host string, insecure bool, + tlsConf *TLSHelperConfig) (*grpc.ClientConn, error) { - return grpc.Dial(host, + opts := append([]grpc.DialOption{}, grpc.WithDefaultCallOptions( grpc.MaxCallRecvMsgSize(GrpcMaxSize), grpc.MaxCallSendMsgSize(GrpcMaxSize)), - grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg)), grpc.WithBlock(), grpc.WithTimeout(10*time.Second)) + + if insecure { + opts = append(opts, grpc.WithInsecure()) + } else { + tlsConf.ConfigType = TLSClientConfig + tlsCfg, _, err := GenerateTLSConfig(*tlsConf) + if err != nil { + return nil, err + } + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))) + } + return grpc.Dial(host, opts...) } func CalcDiffs(targetMap map[string]struct{}, existingMap map[string]struct{}) ([]string, From 0e242fd0ef57568a8e1c30b1fa466b273fb0793c Mon Sep 17 00:00:00 2001 From: Manish R Jain Date: Thu, 29 Nov 2018 15:03:51 -0800 Subject: [PATCH 18/30] Manish Review 2 --- dgraph/cmd/live/run.go | 4 ++-- ee/acl/cmd/groups.go | 40 +++++++++++++++++++--------------------- ee/acl/cmd/run.go | 12 +++++++++--- ee/acl/cmd/users.go | 33 ++++++++++----------------------- ee/acl/utils.go | 25 ++++++++++++++----------- x/x.go | 10 ++++------ 6 files changed, 58 insertions(+), 66 deletions(-) diff --git a/dgraph/cmd/live/run.go b/dgraph/cmd/live/run.go index 1808c03eb63..8a06777ac67 100644 --- a/dgraph/cmd/live/run.go +++ b/dgraph/cmd/live/run.go @@ -250,7 +250,7 @@ func setup(opts batchMutationOptions, dc *dgo.Dgraph) *loader { kv, err := badger.Open(o) x.Checkf(err, "Error while creating badger KV posting store") - connzero, err := x.SetupConnection(opt.zero, true, &tlsConf) + connzero, err := x.SetupConnection(opt.zero, &tlsConf) x.Checkf(err, "Unable to connect to zero, Is it running at %s?", opt.zero) alloc := xidmap.New( @@ -310,7 +310,7 @@ func run() error { ds := strings.Split(opt.dgraph, ",") var clients []api.DgraphClient for _, d := range ds { - conn, err := x.SetupConnection(d, !tlsConf.CertRequired, &tlsConf) + conn, err := x.SetupConnection(d, &tlsConf) x.Checkf(err, "While trying to setup connection to Dgraph alpha.") defer conn.Close() diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 5901ffc26f2..649e2115e1b 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -16,7 +16,6 @@ import ( "encoding/json" "fmt" "strings" - "time" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" @@ -25,17 +24,16 @@ import ( "github.com/golang/glog" ) -func groupAdd(dc *dgo.Dgraph) error { +func groupAdd(ctx context.Context, dc *dgo.Dgraph) error { groupId := GroupAdd.Conf.GetString("group") if len(groupId) == 0 { return fmt.Errorf("the group id should not be empty") } - ctx := context.Background() txn := dc.NewTxn() defer txn.Discard(ctx) - group, err := queryGroup(ctx, txn, groupId, "uid") + group, err := queryGroup(ctx, txn, groupId) if err != nil { return err } @@ -66,19 +64,16 @@ func groupAdd(dc *dgo.Dgraph) error { return nil } -func groupDel(dc *dgo.Dgraph) error { +func groupDel(ctx context.Context, dc *dgo.Dgraph) error { groupId := GroupDel.Conf.GetString("group") if len(groupId) == 0 { return fmt.Errorf("the group id should not be empty") } - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - txn := dc.NewTxn() defer txn.Discard(ctx) - group, err := queryGroup(ctx, txn, groupId, "uid") + group, err := queryGroup(ctx, txn, groupId) if err != nil { return err } @@ -110,7 +105,9 @@ func queryGroup(ctx context.Context, txn *dgo.Txn, groupid string, // write query header query := fmt.Sprintf(`query search($groupid: string){ - group(func: eq(dgraph.xid, $groupid)) { %s }}`, strings.Join(fields, ", ")) + group(func: eq(dgraph.xid, $groupid)) { + uid + %s }}`, strings.Join(fields, ", ")) queryVars := make(map[string]string) queryVars["$groupid"] = groupid @@ -120,7 +117,7 @@ func queryGroup(ctx context.Context, txn *dgo.Txn, groupid string, glog.Errorf("Error while query group with id %s: %v", groupid, err) return nil, err } - group, err = acl.UnmarshallGroup(queryResp, "group") + group, err = acl.UnmarshalGroup(queryResp, "group") if err != nil { return nil, err } @@ -132,7 +129,7 @@ type Acl struct { Perm int32 `json:"perm"` } -func chMod(dc *dgo.Dgraph) error { +func chMod(ctx context.Context, dc *dgo.Dgraph) error { groupId := ChMod.Conf.GetString("group") predicate := ChMod.Conf.GetString("pred") perm := ChMod.Conf.GetInt("perm") @@ -143,23 +140,23 @@ func chMod(dc *dgo.Dgraph) error { return fmt.Errorf("the predicate must not be empty") } - ctx := context.Background() txn := dc.NewTxn() defer txn.Discard(ctx) - group, err := queryGroup(txn, ctx, groupId, "uid", "dgraph.group.acl") + group, err := queryGroup(ctx, txn, groupId, "dgraph.group.acl") if err != nil { return err } - if group == nil || len(group.Uid) == 0 { - return fmt.Errorf("Unable to change permission for group because it does not exist: %v", groupId) + return fmt.Errorf("Unable to change permission for group because it does not exist: %v", + groupId) } currentAcls := []Acl{} if len(group.Acls) != 0 { if err := json.Unmarshal([]byte(group.Acls), ¤tAcls); err != nil { - return fmt.Errorf("Unable to unmarshal the acls associated with the group %v:%v", groupId, err) + return fmt.Errorf("Unable to unmarshal the acls associated with the group %v:%v", + groupId, err) } } @@ -187,11 +184,12 @@ func chMod(dc *dgo.Dgraph) error { Set: []*api.NQuad{chModNQuads}, } - _, err = txn.Mutate(ctx, mu) - if err != nil { - return fmt.Errorf("Unable to change mutations for the group %v on predicate %v: %v", groupId, predicate, err) + if _, err = txn.Mutate(ctx, mu); err != nil { + return fmt.Errorf("Unable to change mutations for the group %v on predicate %v: %v", + groupId, predicate, err) } - glog.Infof("Successfully changed permission for group %v on predicate %v to %v", groupId, predicate, perm) + glog.Infof("Successfully changed permission for group %v on predicate %v to %v", + groupId, predicate, perm) return nil } diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go index 3b6f89b7c5b..2ca3599a5fc 100644 --- a/ee/acl/cmd/run.go +++ b/ee/acl/cmd/run.go @@ -13,8 +13,10 @@ package acl import ( + "context" "os" "strings" + "time" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" @@ -157,7 +159,7 @@ func initSubcommands() { "an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") } -func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { +func runTxn(conf *viper.Viper, f func(ctx context.Context, dc *dgo.Dgraph) error) { opt = options{ dgraph: conf.GetString("dgraph"), } @@ -173,7 +175,7 @@ func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { ds := strings.Split(opt.dgraph, ",") var clients []api.DgraphClient for _, d := range ds { - conn, err := x.SetupConnection(d, !tlsConf.CertRequired, &tlsConf) + conn, err := x.SetupConnection(d, &tlsConf) x.Checkf(err, "While trying to setup connection to Dgraph alpha.") defer conn.Close() @@ -181,7 +183,11 @@ func runTxn(conf *viper.Viper, f func(dgraph *dgo.Dgraph) error) { clients = append(clients, dc) } dgraphClient := dgo.NewDgraphClient(clients...) - if err := f(dgraphClient); err != nil { + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := f(ctx, dgraphClient); err != nil { glog.Errorf("Error while running transaction: %v", err) os.Exit(1) } diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index e6a42104427..5d4e9475151 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -24,7 +24,7 @@ import ( "github.com/golang/glog" ) -func userAdd(dc *dgo.Dgraph) error { +func userAdd(ctx context.Context, dc *dgo.Dgraph) error { userid := UserAdd.Conf.GetString("user") password := UserAdd.Conf.GetString("password") @@ -35,15 +35,12 @@ func userAdd(dc *dgo.Dgraph) error { return fmt.Errorf("The password must not be empty.") } - ctx := context.Background() txn := dc.NewTxn() defer txn.Discard(ctx) - user, err := queryUser(ctx, txn, userid) if err != nil { return err } - if user != nil { return fmt.Errorf("Unable to create user because of conflict: %v", userid) } @@ -65,8 +62,7 @@ func userAdd(dc *dgo.Dgraph) error { Set: createUserNQuads, } - _, err = txn.Mutate(ctx, mu) - if err != nil { + if _, err := txn.Mutate(ctx, mu); err != nil { return fmt.Errorf("Unable to create user: %v", err) } @@ -74,14 +70,13 @@ func userAdd(dc *dgo.Dgraph) error { return nil } -func userDel(dc *dgo.Dgraph) error { +func userDel(ctx context.Context, dc *dgo.Dgraph) error { userid := UserDel.Conf.GetString("user") // validate the userid if len(userid) == 0 { return fmt.Errorf("The user id should not be empty") } - ctx := context.Background() txn := dc.NewTxn() defer txn.Discard(ctx) @@ -106,8 +101,7 @@ func userDel(dc *dgo.Dgraph) error { Del: deleteUserNQuads, } - _, err = txn.Mutate(ctx, mu) - if err != nil { + if _, err = txn.Mutate(ctx, mu); err != nil { return fmt.Errorf("Unable to delete user: %v", err) } @@ -115,7 +109,7 @@ func userDel(dc *dgo.Dgraph) error { return nil } -func userLogin(dc *dgo.Dgraph) error { +func userLogin(ctx context.Context, dc *dgo.Dgraph) error { userid := LogIn.Conf.GetString("user") password := LogIn.Conf.GetString("password") @@ -126,10 +120,7 @@ func userLogin(dc *dgo.Dgraph) error { return fmt.Errorf("The password must not be empty.") } - ctx := context.Background() - - err := dc.Login(ctx, userid, password) - if err != nil { + if err := dc.Login(ctx, userid, password); err != nil { return fmt.Errorf("Unable to login:%v", err) } glog.Infof("Login successfully with jwt:\n%v", dc.GetJwt()) @@ -155,21 +146,20 @@ func queryUser(ctx context.Context, txn *dgo.Txn, userid string) (user *acl.User if err != nil { return nil, fmt.Errorf("Error while query user with id %s: %v", userid, err) } - user, err = acl.UnmarshallUser(queryResp, "user") + user, err = acl.UnmarshalUser(queryResp, "user") if err != nil { return nil, err } return user, nil } -func userMod(dc *dgo.Dgraph) error { +func userMod(ctx context.Context, dc *dgo.Dgraph) error { userid := UserMod.Conf.GetString("user") groups := UserMod.Conf.GetString("groups") if len(userid) == 0 { return fmt.Errorf("The user must not be empty") } - ctx := context.Background() txn := dc.NewTxn() defer txn.Discard(ctx) @@ -207,7 +197,6 @@ func userMod(dc *dgo.Dgraph) error { if err != nil { return fmt.Errorf("error while getting the user mod nquad:%v", err) } - mu.Set = append(mu.Set, nquad) } @@ -224,18 +213,16 @@ func userMod(dc *dgo.Dgraph) error { return nil } - _, err = txn.Mutate(ctx, mu) - if err != nil { + if _, err := txn.Mutate(ctx, mu); err != nil { return err } - glog.Infof("Successfully modifed groups for user %v", userid) return nil } func getUserModNQuad(ctx context.Context, txn *dgo.Txn, useruid string, groupid string) (*api.NQuad, error) { - group, err := queryGroup(txn, ctx, groupid, []string{"uid"}) + group, err := queryGroup(ctx, txn, groupid, "uid") if err != nil { return nil, err } diff --git a/ee/acl/utils.go b/ee/acl/utils.go index dba157667e6..3f1b33b3581 100644 --- a/ee/acl/utils.go +++ b/ee/acl/utils.go @@ -15,7 +15,9 @@ package acl import ( "encoding/json" "fmt" + "github.com/dgraph-io/dgo/protos/api" + "github.com/dgraph-io/dgraph/x" "github.com/golang/glog" ) @@ -32,9 +34,8 @@ func ValidateLoginRequest(request *api.LogInRequest) error { return nil } - func ToJwtGroups(groups []Group) []JwtGroup { - if groups == nil { + if len(groups) == 0 { // the user does not have any groups return nil } @@ -48,7 +49,6 @@ func ToJwtGroups(groups []Group) []JwtGroup { return jwtGroups } - type User struct { Uid string `json:"uid"` UserID string `json:"dgraph.xid"` @@ -58,10 +58,10 @@ type User struct { } // Extract the first User pointed by the userKey in the query response -func UnmarshallUser(queryResp *api.Response, userKey string) (user *User, err error) { +func UnmarshalUser(resp *api.Response, userKey string) (user *User, err error) { m := make(map[string][]User) - err = json.Unmarshal(queryResp.GetJson(), &m) + err = json.Unmarshal(resp.GetJson(), &m) if err != nil { return nil, fmt.Errorf("Unable to unmarshal the query user response for user:%v", err) } @@ -70,7 +70,9 @@ func UnmarshallUser(queryResp *api.Response, userKey string) (user *User, err er // the user does not exist return nil, nil } - + if len(users) > 1 { + return nil, x.Errorf("Found multiple users: %s", resp.GetJson()) + } return &users[0], nil } @@ -81,12 +83,11 @@ type Group struct { Acls string `json:"dgraph.group.acl"` } - // Extract the first User pointed by the userKey in the query response -func UnmarshallGroup(queryResp *api.Response, groupKey string) (group *Group, err error) { +func UnmarshalGroup(resp *api.Response, groupKey string) (group *Group, err error) { m := make(map[string][]Group) - err = json.Unmarshal(queryResp.GetJson(), &m) + err = json.Unmarshal(resp.GetJson(), &m) if err != nil { glog.Errorf("Unable to unmarshal the query group response:%v", err) return nil, err @@ -96,6 +97,8 @@ func UnmarshallGroup(queryResp *api.Response, groupKey string) (group *Group, er // the group does not exist return nil, nil } - + if len(groups) > 1 { + return nil, x.Errorf("Found multiple groups: %s", resp.GetJson()) + } return &groups[0], nil -} \ No newline at end of file +} diff --git a/x/x.go b/x/x.go index 4fe090fdadf..304524008d3 100644 --- a/x/x.go +++ b/x/x.go @@ -423,9 +423,7 @@ func DivideAndRule(num int) (numGo, width int) { return } -func SetupConnection(host string, insecure bool, - tlsConf *TLSHelperConfig) (*grpc.ClientConn, error) { - +func SetupConnection(host string, tlsConf *TLSHelperConfig) (*grpc.ClientConn, error) { opts := append([]grpc.DialOption{}, grpc.WithDefaultCallOptions( grpc.MaxCallRecvMsgSize(GrpcMaxSize), @@ -433,15 +431,15 @@ func SetupConnection(host string, insecure bool, grpc.WithBlock(), grpc.WithTimeout(10*time.Second)) - if insecure { - opts = append(opts, grpc.WithInsecure()) - } else { + if tlsConf.CertRequired { tlsConf.ConfigType = TLSClientConfig tlsCfg, _, err := GenerateTLSConfig(*tlsConf) if err != nil { return nil, err } opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsCfg))) + } else { + opts = append(opts, grpc.WithInsecure()) } return grpc.Dial(host, opts...) } From e51a5ad17374933d3cc1b668039c2116eac2cb81 Mon Sep 17 00:00:00 2001 From: Manish R Jain Date: Thu, 29 Nov 2018 15:34:33 -0800 Subject: [PATCH 19/30] Manish Review 3 --- edgraph/access_ee.go | 29 +++++++++++++++++------------ ee/acl/acl_test.go | 2 +- ee/acl/cmd/groups.go | 18 ++++++++---------- ee/acl/cmd/users.go | 2 +- x/x.go | 2 +- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go index 0a83865c746..6a4ee72e164 100644 --- a/edgraph/access_ee.go +++ b/edgraph/access_ee.go @@ -15,11 +15,12 @@ package edgraph import ( "context" "fmt" + "time" + "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/ee/acl" "github.com/golang/glog" "google.golang.org/grpc/peer" - "time" otrace "go.opencensus.io/trace" ) @@ -30,14 +31,17 @@ func (s *Server) Login(ctx context.Context, defer span.End() // record the client ip for this login request - clientPeer, ok := peer.FromContext(ctx) - if ok { + var addr string + if ip, ok := peer.FromContext(ctx); ok { + addr = ip.Addr.String() + glog.Infof("Login request from: %s", addr) span.Annotate([]otrace.Attribute{ - otrace.StringAttribute("client_ip", clientPeer.Addr.String()), + otrace.StringAttribute("client_ip", addr), }, "client ip for login") } if err := acl.ValidateLoginRequest(request); err != nil { + glog.Warningf("Invalid login from: %s", addr) return nil, err } @@ -48,16 +52,16 @@ func (s *Server) Login(ctx context.Context, user, err := s.queryUser(ctx, request.Userid, request.Password) if err != nil { - glog.Warningf("Unable to login user with user id: %v", request.Userid) + glog.Warningf("Unable to login user id: %v. Addr: %s", request.Userid, addr) return nil, err } if user == nil { - errMsg := fmt.Sprintf("User not found for user id %v", request.Userid) + errMsg := fmt.Sprintf("User not found for user id %v. Addr: %s", request.Userid, addr) glog.Warningf(errMsg) return nil, fmt.Errorf(errMsg) } if !user.PasswordMatch { - errMsg := fmt.Sprintf("Password mismatch for user: %v", request.Userid) + errMsg := fmt.Sprintf("Password mismatch for user: %v. Addr: %s", request.Userid, addr) glog.Warningf(errMsg) return nil, fmt.Errorf(errMsg) } @@ -96,9 +100,10 @@ const queryUser = ` func (s *Server) queryUser(ctx context.Context, userid string, password string) (user *acl.User, err error) { - queryVars := make(map[string]string) - queryVars["$userid"] = userid - queryVars["$password"] = password + queryVars := map[string]string{ + "$userid": userid, + "$password": password, + } queryRequest := api.Request{ Query: queryUser, Vars: queryVars, @@ -109,9 +114,9 @@ func (s *Server) queryUser(ctx context.Context, userid string, password string) glog.Errorf("Error while query user with id %s: %v", userid, err) return nil, err } - user, err = acl.UnmarshallUser(queryResp, "user") + user, err = acl.UnmarshalUser(queryResp, "user") if err != nil { return nil, err } return user, nil -} \ No newline at end of file +} diff --git a/ee/acl/acl_test.go b/ee/acl/acl_test.go index 005e974e2e5..806c8fcc78c 100644 --- a/ee/acl/acl_test.go +++ b/ee/acl/acl_test.go @@ -25,7 +25,7 @@ const ( func TestAcl(t *testing.T) { t.Run("create user", CreateAndDeleteUsers) - t.Run("login", LogIn) + // t.Run("login", LogIn) } func checkOutput(t *testing.T, cmd *exec.Cmd, shouldFail bool) string { diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 649e2115e1b..913dfffe01b 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -37,7 +37,6 @@ func groupAdd(ctx context.Context, dc *dgo.Dgraph) error { if err != nil { return err } - if group != nil { return fmt.Errorf("The group with id %v already exists.", groupId) } @@ -54,9 +53,7 @@ func groupAdd(ctx context.Context, dc *dgo.Dgraph) error { CommitNow: true, Set: createGroupNQuads, } - - _, err = txn.Mutate(ctx, mu) - if err != nil { + if _, err = txn.Mutate(ctx, mu); err != nil { return fmt.Errorf("Unable to create user: %v", err) } @@ -109,10 +106,11 @@ func queryGroup(ctx context.Context, txn *dgo.Txn, groupid string, uid %s }}`, strings.Join(fields, ", ")) - queryVars := make(map[string]string) - queryVars["$groupid"] = groupid + queryVars := map[string]string{ + "$groupid": groupid, + } - queryResp, err := txn.QueryWithVars(context.Background(), query, queryVars) + queryResp, err := txn.QueryWithVars(ctx, query, queryVars) if err != nil { glog.Errorf("Error while query group with id %s: %v", groupid, err) return nil, err @@ -160,7 +158,7 @@ func chMod(ctx context.Context, dc *dgo.Dgraph) error { } } - newAcls, updated := updateAcl(currentAcls, &Acl{ + newAcls, updated := updateAcl(currentAcls, Acl{ Predicate: predicate, Perm: int32(perm), }) @@ -194,7 +192,7 @@ func chMod(ctx context.Context, dc *dgo.Dgraph) error { } // returns whether the existing acls slice is changed -func updateAcl(acls []Acl, newAcl *Acl) ([]Acl, bool) { +func updateAcl(acls []Acl, newAcl Acl) ([]Acl, bool) { for idx, acl := range acls { if acl.Predicate == newAcl.Predicate { if acl.Perm == newAcl.Perm { @@ -211,5 +209,5 @@ func updateAcl(acls []Acl, newAcl *Acl) ([]Acl, bool) { } // we do not find any existing acl matching the newAcl predicate - return append(acls, *newAcl), true + return append(acls, newAcl), true } diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 5d4e9475151..28b4e72bb51 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -183,7 +183,7 @@ func userMod(ctx context.Context, dc *dgo.Dgraph) error { for _, g := range user.Groups { existingGroupsMap[g.GroupID] = exists } - newGroups, groupsToBeDeleted := x.CalcDiffs(targetGroupsMap, existingGroupsMap) + newGroups, groupsToBeDeleted := x.Diff(targetGroupsMap, existingGroupsMap) mu := &api.Mutation{ CommitNow: true, diff --git a/x/x.go b/x/x.go index 304524008d3..3548b29c8b1 100644 --- a/x/x.go +++ b/x/x.go @@ -444,7 +444,7 @@ func SetupConnection(host string, tlsConf *TLSHelperConfig) (*grpc.ClientConn, e return grpc.Dial(host, opts...) } -func CalcDiffs(targetMap map[string]struct{}, existingMap map[string]struct{}) ([]string, +func Diff(targetMap map[string]struct{}, existingMap map[string]struct{}) ([]string, []string) { var newGroups []string var groupsToBeDeleted []string From b1307000db0d833056433c823d56438b9ab83fa6 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 29 Nov 2018 18:43:07 -0800 Subject: [PATCH 20/30] changed runTxn to a helper func --- edgraph/access_ee.go | 11 ++--- ee/acl/cmd/groups.go | 61 ++++++++++++++++----------- ee/acl/cmd/run.go | 49 +++++++++++++--------- ee/acl/cmd/users.go | 99 ++++++++++++++++++++++++++------------------ 4 files changed, 129 insertions(+), 91 deletions(-) diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go index 6a4ee72e164..146d3bb2b1d 100644 --- a/edgraph/access_ee.go +++ b/edgraph/access_ee.go @@ -26,7 +26,7 @@ import ( ) func (s *Server) Login(ctx context.Context, - request *api.LogInRequest) (*api.LogInResponse, error) { + request *api.LogInRequest) (*api.Response, error) { ctx, span := otrace.StartSpan(ctx, "server.LogIn") defer span.End() @@ -45,10 +45,7 @@ func (s *Server) Login(ctx context.Context, return nil, err } - resp := &api.LogInResponse{ - Context: &api.TxnContext{}, // context needed in order to set the jwt below - Code: api.AclResponseCode_UNAUTHENTICATED, - } + resp := &api.Response{} user, err := s.queryUser(ctx, request.Userid, request.Password) if err != nil { @@ -76,13 +73,13 @@ func (s *Server) Login(ctx context.Context, }, } - resp.Context.Jwt, err = jwt.EncodeToString(Config.HmacSecret) + jwtString, err := jwt.EncodeToString(Config.HmacSecret) if err != nil { glog.Errorf("Unable to encode jwt to string: %v", err) return nil, err } - resp.Code = api.AclResponseCode_OK + resp.Json = []byte(jwtString) return resp, nil } diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 913dfffe01b..498ce1dc0bd 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -16,29 +16,34 @@ import ( "encoding/json" "fmt" "strings" + "time" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/ee/acl" "github.com/dgraph-io/dgraph/x" "github.com/golang/glog" + "github.com/spf13/viper" ) -func groupAdd(ctx context.Context, dc *dgo.Dgraph) error { - groupId := GroupAdd.Conf.GetString("group") +func groupAdd(conf *viper.Viper) error { + groupId := conf.GetString("group") if len(groupId) == 0 { return fmt.Errorf("the group id should not be empty") } + dc := getDgraphClient(conf) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() txn := dc.NewTxn() defer txn.Discard(ctx) group, err := queryGroup(ctx, txn, groupId) if err != nil { - return err + return fmt.Errorf("error while querying group:%v", err) } if group != nil { - return fmt.Errorf("The group with id %v already exists.", groupId) + return fmt.Errorf("the group with id %v already exists", groupId) } createGroupNQuads := []*api.NQuad{ @@ -54,28 +59,31 @@ func groupAdd(ctx context.Context, dc *dgo.Dgraph) error { Set: createGroupNQuads, } if _, err = txn.Mutate(ctx, mu); err != nil { - return fmt.Errorf("Unable to create user: %v", err) + return fmt.Errorf("unable to create group: %v", err) } glog.Infof("Created new group with id %v", groupId) return nil } -func groupDel(ctx context.Context, dc *dgo.Dgraph) error { - groupId := GroupDel.Conf.GetString("group") +func groupDel(conf *viper.Viper) error { + groupId := conf.GetString("group") if len(groupId) == 0 { return fmt.Errorf("the group id should not be empty") } + dc := getDgraphClient(conf) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() txn := dc.NewTxn() defer txn.Discard(ctx) group, err := queryGroup(ctx, txn, groupId) if err != nil { - return err + return fmt.Errorf("error while querying group:%v", err) } if group == nil || len(group.Uid) == 0 { - return fmt.Errorf("Unable to delete group because it does not exist: %v", groupId) + return fmt.Errorf("unable to delete group because it does not exist: %v", groupId) } deleteGroupNQuads := []*api.NQuad{ @@ -90,10 +98,10 @@ func groupDel(ctx context.Context, dc *dgo.Dgraph) error { Del: deleteGroupNQuads, } if _, err := txn.Mutate(ctx, mu); err != nil { - return fmt.Errorf("Unable to delete group: %v", err) + return fmt.Errorf("unable to delete group: %v", err) } - glog.Infof("Deleted user with id %v", groupId) + glog.Infof("Deleted group with id %v", groupId) return nil } @@ -127,9 +135,9 @@ type Acl struct { Perm int32 `json:"perm"` } -func chMod(ctx context.Context, dc *dgo.Dgraph) error { - groupId := ChMod.Conf.GetString("group") - predicate := ChMod.Conf.GetString("pred") +func chMod(conf *viper.Viper) error { + groupId := conf.GetString("group") + predicate := conf.GetString("pred") perm := ChMod.Conf.GetInt("perm") if len(groupId) == 0 { return fmt.Errorf("the groupid must not be empty") @@ -138,22 +146,25 @@ func chMod(ctx context.Context, dc *dgo.Dgraph) error { return fmt.Errorf("the predicate must not be empty") } + dc := getDgraphClient(conf) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() txn := dc.NewTxn() defer txn.Discard(ctx) group, err := queryGroup(ctx, txn, groupId, "dgraph.group.acl") if err != nil { - return err + return fmt.Errorf("error while querying group:%v", err) } if group == nil || len(group.Uid) == 0 { - return fmt.Errorf("Unable to change permission for group because it does not exist: %v", + return fmt.Errorf("unable to change permission for group because it does not exist: %v", groupId) } - currentAcls := []Acl{} + var currentAcls []Acl if len(group.Acls) != 0 { if err := json.Unmarshal([]byte(group.Acls), ¤tAcls); err != nil { - return fmt.Errorf("Unable to unmarshal the acls associated with the group %v:%v", + return fmt.Errorf("unable to unmarshal the acls associated with the group %v:%v", groupId, err) } } @@ -169,7 +180,7 @@ func chMod(ctx context.Context, dc *dgo.Dgraph) error { newAclBytes, err := json.Marshal(newAcls) if err != nil { - return fmt.Errorf("Unable to marshal the updated acls:%v", err) + return fmt.Errorf("unable to marshal the updated acls:%v", err) } chModNQuads := &api.NQuad{ @@ -183,7 +194,7 @@ func chMod(ctx context.Context, dc *dgo.Dgraph) error { } if _, err = txn.Mutate(ctx, mu); err != nil { - return fmt.Errorf("Unable to change mutations for the group %v on predicate %v: %v", + return fmt.Errorf("unable to change mutations for the group %v on predicate %v: %v", groupId, predicate, err) } glog.Infof("Successfully changed permission for group %v on predicate %v to %v", @@ -193,13 +204,13 @@ func chMod(ctx context.Context, dc *dgo.Dgraph) error { // returns whether the existing acls slice is changed func updateAcl(acls []Acl, newAcl Acl) ([]Acl, bool) { - for idx, acl := range acls { - if acl.Predicate == newAcl.Predicate { - if acl.Perm == newAcl.Perm { + for idx, aclEntry := range acls { + if aclEntry.Predicate == newAcl.Predicate { + if aclEntry.Perm == newAcl.Perm { return acls, false } if newAcl.Perm < 0 { - // remove the current acl from the array + // remove the current aclEntry from the array copy(acls[idx:], acls[idx+1:]) return acls[:len(acls)-1], true } @@ -208,6 +219,6 @@ func updateAcl(acls []Acl, newAcl Acl) ([]Acl, bool) { } } - // we do not find any existing acl matching the newAcl predicate + // we do not find any existing aclEntry matching the newAcl predicate return append(acls, newAcl), true } diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go index 2ca3599a5fc..c2fd4c2702b 100644 --- a/ee/acl/cmd/run.go +++ b/ee/acl/cmd/run.go @@ -13,10 +13,8 @@ package acl import ( - "context" "os" "strings" - "time" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" @@ -78,7 +76,10 @@ func initSubcommands() { Use: "useradd", Short: "Run Dgraph acl tool to add a user", Run: func(cmd *cobra.Command, args []string) { - runTxn(UserAdd.Conf, userAdd) + if err := userAdd(UserAdd.Conf); err != nil { + glog.Errorf("Unable to add user:%v", err) + os.Exit(1) + } }, } userAddFlags := UserAdd.Cmd.Flags() @@ -90,7 +91,10 @@ func initSubcommands() { Use: "userdel", Short: "Run Dgraph acl tool to delete a user", Run: func(cmd *cobra.Command, args []string) { - runTxn(UserDel.Conf, userDel) + if err := userDel(UserDel.Conf); err != nil { + glog.Errorf("Unable to delete the user:%v", err) + os.Exit(1) + } }, } userDelFlags := UserDel.Cmd.Flags() @@ -101,7 +105,10 @@ func initSubcommands() { Use: "login", Short: "Login to dgraph in order to get a jwt token", Run: func(cmd *cobra.Command, args []string) { - runTxn(LogIn.Conf, userLogin) + if err := userLogin(LogIn.Conf); err != nil { + glog.Errorf("Unable to login:%v", err) + os.Exit(1) + } }, } loginFlags := LogIn.Cmd.Flags() @@ -113,7 +120,10 @@ func initSubcommands() { Use: "groupadd", Short: "Run Dgraph acl tool to add a group", Run: func(cmd *cobra.Command, args []string) { - runTxn(GroupAdd.Conf, groupAdd) + if err := groupAdd(GroupAdd.Conf); err != nil { + glog.Errorf("Unable to add group:%v", err) + os.Exit(1) + } }, } groupAddFlags := GroupAdd.Cmd.Flags() @@ -124,7 +134,10 @@ func initSubcommands() { Use: "groupdel", Short: "Run Dgraph acl tool to delete a group", Run: func(cmd *cobra.Command, args []string) { - runTxn(GroupDel.Conf, groupDel) + if err := groupDel(GroupDel.Conf); err != nil { + glog.Errorf("Unable to delete group:%v", err) + os.Exit(1) + } }, } groupDelFlags := GroupDel.Cmd.Flags() @@ -135,7 +148,10 @@ func initSubcommands() { Use: "usermod", Short: "Run Dgraph acl tool to change a user's groups", Run: func(cmd *cobra.Command, args []string) { - runTxn(UserMod.Conf, userMod) + if err := userMod(UserMod.Conf); err != nil { + glog.Errorf("Unable to modify user:%v", err) + os.Exit(1) + } }, } userModFlags := UserMod.Cmd.Flags() @@ -147,7 +163,10 @@ func initSubcommands() { Use: "chmod", Short: "Run Dgraph acl tool to change a group's permissions", Run: func(cmd *cobra.Command, args []string) { - runTxn(ChMod.Conf, chMod) + if err := chMod(ChMod.Conf); err != nil { + glog.Errorf("Unable to change permisson for group:%v", err) + os.Exit(1) + } }, } chModFlags := ChMod.Cmd.Flags() @@ -159,7 +178,7 @@ func initSubcommands() { "an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") } -func runTxn(conf *viper.Viper, f func(ctx context.Context, dc *dgo.Dgraph) error) { +func getDgraphClient(conf *viper.Viper) *dgo.Dgraph { opt = options{ dgraph: conf.GetString("dgraph"), } @@ -177,18 +196,10 @@ func runTxn(conf *viper.Viper, f func(ctx context.Context, dc *dgo.Dgraph) error for _, d := range ds { conn, err := x.SetupConnection(d, &tlsConf) x.Checkf(err, "While trying to setup connection to Dgraph alpha.") - defer conn.Close() dc := api.NewDgraphClient(conn) clients = append(clients, dc) } - dgraphClient := dgo.NewDgraphClient(clients...) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - if err := f(ctx, dgraphClient); err != nil { - glog.Errorf("Error while running transaction: %v", err) - os.Exit(1) - } + return dgo.NewDgraphClient(clients...) } diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 28b4e72bb51..b7dcfb5a699 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -16,33 +16,39 @@ import ( "context" "fmt" "strings" + "time" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/ee/acl" "github.com/dgraph-io/dgraph/x" "github.com/golang/glog" + "github.com/spf13/viper" ) -func userAdd(ctx context.Context, dc *dgo.Dgraph) error { - userid := UserAdd.Conf.GetString("user") - password := UserAdd.Conf.GetString("password") +func userAdd(conf *viper.Viper) error { + userid := conf.GetString("user") + password := conf.GetString("password") if len(userid) == 0 { - return fmt.Errorf("The user must not be empty.") + return fmt.Errorf("the user must not be empty") } if len(password) == 0 { - return fmt.Errorf("The password must not be empty.") + return fmt.Errorf("the password must not be empty") } + dc := getDgraphClient(conf) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() txn := dc.NewTxn() defer txn.Discard(ctx) + user, err := queryUser(ctx, txn, userid) if err != nil { - return err + return fmt.Errorf("error while querying user:%v", err) } if user != nil { - return fmt.Errorf("Unable to create user because of conflict: %v", userid) + return fmt.Errorf("unable to create user because of conflict: %v", userid) } createUserNQuads := []*api.NQuad{ @@ -63,30 +69,33 @@ func userAdd(ctx context.Context, dc *dgo.Dgraph) error { } if _, err := txn.Mutate(ctx, mu); err != nil { - return fmt.Errorf("Unable to create user: %v", err) + return fmt.Errorf("unable to create user: %v", err) } glog.Infof("Created new user with id %v", userid) return nil } -func userDel(ctx context.Context, dc *dgo.Dgraph) error { - userid := UserDel.Conf.GetString("user") +func userDel(conf *viper.Viper) error { + userid := conf.GetString("user") // validate the userid if len(userid) == 0 { - return fmt.Errorf("The user id should not be empty") + return fmt.Errorf("the user id should not be empty") } + dc := getDgraphClient(conf) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() txn := dc.NewTxn() defer txn.Discard(ctx) user, err := queryUser(ctx, txn, userid) if err != nil { - return err + return fmt.Errorf("error while querying user:%v", err) } if user == nil || len(user.Uid) == 0 { - return fmt.Errorf("Unable to delete user because it does not exist: %v", userid) + return fmt.Errorf("unable to delete user because it does not exist: %v", userid) } deleteUserNQuads := []*api.NQuad{ @@ -102,28 +111,35 @@ func userDel(ctx context.Context, dc *dgo.Dgraph) error { } if _, err = txn.Mutate(ctx, mu); err != nil { - return fmt.Errorf("Unable to delete user: %v", err) + return fmt.Errorf("unable to delete user: %v", err) } glog.Infof("Deleted user with id %v", userid) return nil } -func userLogin(ctx context.Context, dc *dgo.Dgraph) error { - userid := LogIn.Conf.GetString("user") - password := LogIn.Conf.GetString("password") +func userLogin(conf *viper.Viper) error { + userid := conf.GetString("user") + password := conf.GetString("password") if len(userid) == 0 { - return fmt.Errorf("The user must not be empty.") + return fmt.Errorf("the user must not be empty") } if len(password) == 0 { - return fmt.Errorf("The password must not be empty.") + return fmt.Errorf("the password must not be empty") } + dc := getDgraphClient(conf) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + txn := dc.NewTxn() + defer txn.Discard(ctx) + if err := dc.Login(ctx, userid, password); err != nil { - return fmt.Errorf("Unable to login:%v", err) + return fmt.Errorf("unable to login:%v", err) } - glog.Infof("Login successfully with jwt:\n%v", dc.GetJwt()) + updatedContext := dc.GetContext(ctx) + glog.Infof("Login successfully with jwt:\n%v", updatedContext.Value("jwt")) return nil } @@ -144,7 +160,7 @@ func queryUser(ctx context.Context, txn *dgo.Txn, userid string) (user *acl.User queryResp, err := txn.QueryWithVars(ctx, query, queryVars) if err != nil { - return nil, fmt.Errorf("Error while query user with id %s: %v", userid, err) + return nil, fmt.Errorf("error while query user with id %s: %v", userid, err) } user, err = acl.UnmarshalUser(queryResp, "user") if err != nil { @@ -153,22 +169,25 @@ func queryUser(ctx context.Context, txn *dgo.Txn, userid string) (user *acl.User return user, nil } -func userMod(ctx context.Context, dc *dgo.Dgraph) error { - userid := UserMod.Conf.GetString("user") - groups := UserMod.Conf.GetString("groups") - if len(userid) == 0 { - return fmt.Errorf("The user must not be empty") +func userMod(conf *viper.Viper) error { + userId := conf.GetString("user") + groups := conf.GetString("groups") + if len(userId) == 0 { + return fmt.Errorf("the user must not be empty") } + dc := getDgraphClient(conf) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() txn := dc.NewTxn() defer txn.Discard(ctx) - user, err := queryUser(ctx, txn, userid) + user, err := queryUser(ctx, txn, userId) if err != nil { - return err + return fmt.Errorf("error while querying user:%v", err) } if user == nil { - return fmt.Errorf("The user does not exist: %v", userid) + return fmt.Errorf("the user does not exist: %v", userId) } targetGroupsMap := make(map[string]struct{}) @@ -192,7 +211,7 @@ func userMod(ctx context.Context, dc *dgo.Dgraph) error { } for _, g := range newGroups { - glog.Infof("Adding user %v to group %v", userid, g) + glog.Infof("Adding user %v to group %v", userId, g) nquad, err := getUserModNQuad(ctx, txn, user.Uid, g) if err != nil { return fmt.Errorf("error while getting the user mod nquad:%v", err) @@ -201,7 +220,7 @@ func userMod(ctx context.Context, dc *dgo.Dgraph) error { } for _, g := range groupsToBeDeleted { - glog.Infof("Deleting user %v from group %v", userid, g) + glog.Infof("Deleting user %v from group %v", userId, g) nquad, err := getUserModNQuad(ctx, txn, user.Uid, g) if err != nil { return fmt.Errorf("error while getting the user mod nquad:%v", err) @@ -209,29 +228,29 @@ func userMod(ctx context.Context, dc *dgo.Dgraph) error { mu.Del = append(mu.Del, nquad) } if len(mu.Del) == 0 && len(mu.Set) == 0 { - glog.Infof("Nothing nees to be changed for the groups of user:%v", userid) + glog.Infof("Nothing needs to be changed for the groups of user:%v", userId) return nil } if _, err := txn.Mutate(ctx, mu); err != nil { - return err + return fmt.Errorf("error while mutating the group:%+v", err) } - glog.Infof("Successfully modifed groups for user %v", userid) + glog.Infof("Successfully modified groups for user %v", userId) return nil } -func getUserModNQuad(ctx context.Context, txn *dgo.Txn, useruid string, - groupid string) (*api.NQuad, error) { - group, err := queryGroup(ctx, txn, groupid, "uid") +func getUserModNQuad(ctx context.Context, txn *dgo.Txn, userId string, + groupId string) (*api.NQuad, error) { + group, err := queryGroup(ctx, txn, groupId) if err != nil { return nil, err } if group == nil { - return nil, fmt.Errorf("the group does not exist:%v", groupid) + return nil, fmt.Errorf("the group does not exist:%v", groupId) } createUserGroupNQuads := &api.NQuad{ - Subject: useruid, + Subject: userId, Predicate: "dgraph.user.group", ObjectId: group.Uid, } From 58167b4a10b9864faf0afb14f75c1cd4a2d796ff Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Fri, 30 Nov 2018 14:00:48 -0800 Subject: [PATCH 21/30] Switch to jwt-go and fixing bug --- edgraph/access_ee.go | 21 ++++---- ee/acl/jwt.go | 126 ------------------------------------------- ee/acl/utils.go | 6 ++- 3 files changed, 16 insertions(+), 137 deletions(-) delete mode 100644 ee/acl/jwt.go diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go index 146d3bb2b1d..f2f35d49f4f 100644 --- a/edgraph/access_ee.go +++ b/edgraph/access_ee.go @@ -14,11 +14,14 @@ package edgraph import ( "context" + "encoding/json" "fmt" + "strconv" "time" "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/ee/acl" + jwt "github.com/dgrijalva/jwt-go" "github.com/golang/glog" "google.golang.org/grpc/peer" @@ -63,17 +66,15 @@ func (s *Server) Login(ctx context.Context, return nil, fmt.Errorf(errMsg) } - jwt := &acl.Jwt{ - Header: acl.StdJwtHeader, - Payload: acl.JwtPayload{ - Userid: request.Userid, - Groups: acl.ToJwtGroups(user.Groups), - // TODO add the token refresh mechanism - Exp: time.Now().Add(Config.JwtTtl).Unix(), // set the jwt valid for 30 days - }, - } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "userid": request.Userid, + "groups": acl.ToJwtGroups(user.Groups), + // set the jwt exp according to the ttl + "exp": json.Number( + strconv.FormatInt(time.Now().Add(Config.JwtTtl).Unix(), 10)), + }) - jwtString, err := jwt.EncodeToString(Config.HmacSecret) + jwtString, err := token.SignedString(Config.HmacSecret) if err != nil { glog.Errorf("Unable to encode jwt to string: %v", err) return nil, err diff --git a/ee/acl/jwt.go b/ee/acl/jwt.go deleted file mode 100644 index a2b8370d45e..00000000000 --- a/ee/acl/jwt.go +++ /dev/null @@ -1,126 +0,0 @@ -// +build !oss - -/* - * Copyright 2018 Dgraph Labs, Inc. and Contributors - * - * Licensed under the Dgraph Community License (the "License"); you - * may not use this file except in compliance with the License. You - * may obtain a copy of the License at - * - * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt - */ - -package acl - -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "encoding/json" - "fmt" - "strings" -) - -type JwtHeader struct { - Alg string // the jwt algorithm - Typ string // the header type "JWT" -} - -var StdJwtHeader = JwtHeader{ - Alg: "HS256", - Typ: "JWT", -} - -type JwtGroup struct { - Group string -} - -type JwtPayload struct { - Userid string - Exp int64 // the unix time sinch epoch - Groups []JwtGroup -} - -type Jwt struct { - Header JwtHeader - Payload JwtPayload -} - -// convert the jwt to string in format xxx.yyy.zzz -// where xxx represents the header, yyy represents the payload, and zzz represents the -// HMAC SHA256 signature signed by the key -func (jwt *Jwt) EncodeToString(key []byte) (string, error) { - if len(key) == 0 { - return "", fmt.Errorf("the key should not be empty") - } - - header, err := json.Marshal(jwt.Header) - if err != nil { - return "", err - } - - payload, err := json.Marshal(jwt.Payload) - if err != nil { - return "", err - } - - // generate the signature - mac := hmac.New(sha256.New, key) - if _, err := mac.Write(header); err != nil { - return "", err - } - if _, err := mac.Write(payload); err != nil { - return "", err - } - signature := mac.Sum(nil) - - headerBase64 := base64.StdEncoding.EncodeToString(header) - payloadBase64 := base64.StdEncoding.EncodeToString(payload) - signatureBase64 := base64.StdEncoding.EncodeToString(signature) - return headerBase64 + "." + payloadBase64 + "." + signatureBase64, nil -} - -// Decode the input string into the current Jwt struct, and also verify -// that the signature in the input is valid using the key if checkSignature is true -func (jwt *Jwt) DecodeString(input string, key []byte) error { - components := strings.Split(input, ".") - if len(components) != 3 { - return fmt.Errorf("Input is not in format xxx.yyy.zzz") - } - if len(key) == 0 { - return fmt.Errorf("The key should not be empty") - } - - header, err := base64.StdEncoding.DecodeString(components[0]) - if err != nil { - return fmt.Errorf("Unable to base64 decode the header: %v", components[0]) - } - payload, err := base64.StdEncoding.DecodeString(components[1]) - if err != nil { - return fmt.Errorf("Unable to base64 decode the payload: %v", components[1]) - } - signature, err := base64.StdEncoding.DecodeString(components[2]) - if err != nil { - return fmt.Errorf("Unable to base64 decode the signature: %v", components[2]) - } - - mac := hmac.New(sha256.New, key) - if _, err := mac.Write(header); err != nil { - return fmt.Errorf("Error while writing header to construct signature: %v", err) - } - if _, err := mac.Write(payload); err != nil { - return fmt.Errorf("Error while writing payload to construct signature: %v", err) - } - expectedSignature := mac.Sum(nil) - if !hmac.Equal(signature, expectedSignature) { - return fmt.Errorf("JWT signature mismatch") - } - - if err = json.Unmarshal(header, &jwt.Header); err != nil { - return err - } - if err = json.Unmarshal(payload, &jwt.Payload); err != nil { - return err - } - return nil -} diff --git a/ee/acl/utils.go b/ee/acl/utils.go index 3f1b33b3581..52ed0dbd819 100644 --- a/ee/acl/utils.go +++ b/ee/acl/utils.go @@ -40,7 +40,7 @@ func ToJwtGroups(groups []Group) []JwtGroup { return nil } - jwtGroups := make([]JwtGroup, len(groups)) + jwtGroups := make([]JwtGroup, 0, len(groups)) for _, g := range groups { jwtGroups = append(jwtGroups, JwtGroup{ Group: g.GroupID, @@ -102,3 +102,7 @@ func UnmarshalGroup(resp *api.Response, groupKey string) (group *Group, err erro } return &groups[0], nil } + +type JwtGroup struct { + Group string +} From 02f5ecf94fae6c7d5a957bb66d976f8f998cb190 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Fri, 30 Nov 2018 14:39:36 -0800 Subject: [PATCH 22/30] Addressing Gus's comments --- ee/acl/cmd/groups.go | 2 +- ee/acl/cmd/run.go | 70 +++++++++++++++++++++----------------------- ee/acl/cmd/users.go | 5 ++-- 3 files changed, 36 insertions(+), 41 deletions(-) diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 498ce1dc0bd..125564073af 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -138,7 +138,7 @@ type Acl struct { func chMod(conf *viper.Viper) error { groupId := conf.GetString("group") predicate := conf.GetString("pred") - perm := ChMod.Conf.GetInt("perm") + perm := conf.GetInt("perm") if len(groupId) == 0 { return fmt.Errorf("the groupid must not be empty") } diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go index c2fd4c2702b..21f8536ec53 100644 --- a/ee/acl/cmd/run.go +++ b/ee/acl/cmd/run.go @@ -32,15 +32,6 @@ var opt options var tlsConf x.TLSHelperConfig var CmdAcl x.SubCommand -var UserAdd x.SubCommand -var UserDel x.SubCommand -var LogIn x.SubCommand - -var GroupAdd x.SubCommand -var GroupDel x.SubCommand - -var UserMod x.SubCommand -var ChMod x.SubCommand func init() { CmdAcl.Cmd = &cobra.Command{ @@ -55,12 +46,7 @@ func init() { x.RegisterTLSFlags(flag) flag.String("tls_server_name", "", "Used to verify the server hostname.") - initSubcommands() - - var subcommands = []*x.SubCommand{ - &UserAdd, &UserDel, &LogIn, &GroupAdd, &GroupDel, &UserMod, &ChMod, - } - + subcommands := initSubcommands() for _, sc := range subcommands { CmdAcl.Cmd.AddCommand(sc.Cmd) sc.Conf = viper.New() @@ -70,112 +56,122 @@ func init() { } } -func initSubcommands() { +func initSubcommands() []*x.SubCommand { // user creation command - UserAdd.Cmd = &cobra.Command{ + var cmdUserAdd x.SubCommand + cmdUserAdd.Cmd = &cobra.Command{ Use: "useradd", Short: "Run Dgraph acl tool to add a user", Run: func(cmd *cobra.Command, args []string) { - if err := userAdd(UserAdd.Conf); err != nil { + if err := userAdd(cmdUserAdd.Conf); err != nil { glog.Errorf("Unable to add user:%v", err) os.Exit(1) } }, } - userAddFlags := UserAdd.Cmd.Flags() + userAddFlags := cmdUserAdd.Cmd.Flags() userAddFlags.StringP("user", "u", "", "The user id to be created") userAddFlags.StringP("password", "p", "", "The password for the user") // user deletion command - UserDel.Cmd = &cobra.Command{ + var cmdUserDel x.SubCommand + cmdUserDel.Cmd = &cobra.Command{ Use: "userdel", Short: "Run Dgraph acl tool to delete a user", Run: func(cmd *cobra.Command, args []string) { - if err := userDel(UserDel.Conf); err != nil { + if err := userDel(cmdUserDel.Conf); err != nil { glog.Errorf("Unable to delete the user:%v", err) os.Exit(1) } }, } - userDelFlags := UserDel.Cmd.Flags() + userDelFlags := cmdUserDel.Cmd.Flags() userDelFlags.StringP("user", "u", "", "The user id to be deleted") // login command - LogIn.Cmd = &cobra.Command{ + var cmdLogIn x.SubCommand + cmdLogIn.Cmd = &cobra.Command{ Use: "login", Short: "Login to dgraph in order to get a jwt token", Run: func(cmd *cobra.Command, args []string) { - if err := userLogin(LogIn.Conf); err != nil { + if err := userLogin(cmdLogIn.Conf); err != nil { glog.Errorf("Unable to login:%v", err) os.Exit(1) } }, } - loginFlags := LogIn.Cmd.Flags() + loginFlags := cmdLogIn.Cmd.Flags() loginFlags.StringP("user", "u", "", "The user id to be created") loginFlags.StringP("password", "p", "", "The password for the user") // group creation command - GroupAdd.Cmd = &cobra.Command{ + var cmdGroupAdd x.SubCommand + cmdGroupAdd.Cmd = &cobra.Command{ Use: "groupadd", Short: "Run Dgraph acl tool to add a group", Run: func(cmd *cobra.Command, args []string) { - if err := groupAdd(GroupAdd.Conf); err != nil { + if err := groupAdd(cmdGroupAdd.Conf); err != nil { glog.Errorf("Unable to add group:%v", err) os.Exit(1) } }, } - groupAddFlags := GroupAdd.Cmd.Flags() + groupAddFlags := cmdGroupAdd.Cmd.Flags() groupAddFlags.StringP("group", "g", "", "The group id to be created") // group deletion command - GroupDel.Cmd = &cobra.Command{ + var cmdGroupDel x.SubCommand + cmdGroupDel.Cmd = &cobra.Command{ Use: "groupdel", Short: "Run Dgraph acl tool to delete a group", Run: func(cmd *cobra.Command, args []string) { - if err := groupDel(GroupDel.Conf); err != nil { + if err := groupDel(cmdGroupDel.Conf); err != nil { glog.Errorf("Unable to delete group:%v", err) os.Exit(1) } }, } - groupDelFlags := GroupDel.Cmd.Flags() + groupDelFlags := cmdGroupDel.Cmd.Flags() groupDelFlags.StringP("group", "g", "", "The group id to be deleted") // the usermod command used to set a user's groups - UserMod.Cmd = &cobra.Command{ + var cmdUserMod x.SubCommand + cmdUserMod.Cmd = &cobra.Command{ Use: "usermod", Short: "Run Dgraph acl tool to change a user's groups", Run: func(cmd *cobra.Command, args []string) { - if err := userMod(UserMod.Conf); err != nil { + if err := userMod(cmdUserMod.Conf); err != nil { glog.Errorf("Unable to modify user:%v", err) os.Exit(1) } }, } - userModFlags := UserMod.Cmd.Flags() + userModFlags := cmdUserMod.Cmd.Flags() userModFlags.StringP("user", "u", "", "The user id to be changed") userModFlags.StringP("groups", "g", "", "The groups to be set for the user") // the chmod command is used to change a group's permissions - ChMod.Cmd = &cobra.Command{ + var cmdChMod x.SubCommand + cmdChMod.Cmd = &cobra.Command{ Use: "chmod", Short: "Run Dgraph acl tool to change a group's permissions", Run: func(cmd *cobra.Command, args []string) { - if err := chMod(ChMod.Conf); err != nil { + if err := chMod(cmdChMod.Conf); err != nil { glog.Errorf("Unable to change permisson for group:%v", err) os.Exit(1) } }, } - chModFlags := ChMod.Cmd.Flags() + chModFlags := cmdChMod.Cmd.Flags() chModFlags.StringP("group", "g", "", "The group whose permission "+ "is to be changed") chModFlags.StringP("pred", "p", "", "The predicates whose acls"+ " are to be changed") chModFlags.IntP("perm", "P", 0, "The acl represented using "+ "an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") + return []*x.SubCommand{ + &cmdUserAdd, &cmdUserDel, &cmdLogIn, &cmdGroupAdd, &cmdGroupDel, &cmdUserMod, &cmdChMod, + } } func getDgraphClient(conf *viper.Viper) *dgo.Dgraph { diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index b7dcfb5a699..465158c4439 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -191,16 +191,15 @@ func userMod(conf *viper.Viper) error { } targetGroupsMap := make(map[string]struct{}) - var exists = struct{}{} if len(groups) > 0 { for _, g := range strings.Split(groups, ",") { - targetGroupsMap[g] = exists + targetGroupsMap[g] = struct{}{} } } existingGroupsMap := make(map[string]struct{}) for _, g := range user.Groups { - existingGroupsMap[g.GroupID] = exists + existingGroupsMap[g.GroupID] = struct{}{} } newGroups, groupsToBeDeleted := x.Diff(targetGroupsMap, existingGroupsMap) From 3440ca984d93b51a8c3e0f24f8ee748ae6986093 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Fri, 30 Nov 2018 17:00:09 -0800 Subject: [PATCH 23/30] Fix issues so that make oss gets an empty acl subcommand --- edgraph/access.go | 4 ++-- ee/acl/cmd/{run.go => run_ee.go} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename ee/acl/cmd/{run.go => run_ee.go} (100%) diff --git a/edgraph/access.go b/edgraph/access.go index 21bcb7a9622..f0832c6d31a 100644 --- a/edgraph/access.go +++ b/edgraph/access.go @@ -27,8 +27,8 @@ import ( ) func (s *Server) Login(ctx context.Context, - request *api.LogInRequest) (*api.LogInResponse, error) { + request *api.LogInRequest) (*api.Response, error) { glog.Warningf("Login failed: %s", x.ErrNotSupported) - return &api.LogInResponse{}, x.ErrNotSupported + return &api.Response{}, x.ErrNotSupported } diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run_ee.go similarity index 100% rename from ee/acl/cmd/run.go rename to ee/acl/cmd/run_ee.go From 8b0c2968a872d04693615486ffbf4de439436a44 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Tue, 4 Dec 2018 16:04:14 -0800 Subject: [PATCH 24/30] Added the command to show info about a user or group --- ee/acl/cmd/groups.go | 2 +- ee/acl/cmd/run_ee.go | 88 +++++++++++++++++++++++++++++++++++++++++++- ee/acl/cmd/users.go | 1 + ee/acl/utils.go | 7 ++-- 4 files changed, 93 insertions(+), 5 deletions(-) diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 125564073af..837592c0834 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -123,7 +123,7 @@ func queryGroup(ctx context.Context, txn *dgo.Txn, groupid string, glog.Errorf("Error while query group with id %s: %v", groupid, err) return nil, err } - group, err = acl.UnmarshalGroup(queryResp, "group") + group, err = acl.UnmarshalGroup(queryResp.GetJson(), "group") if err != nil { return nil, err } diff --git a/ee/acl/cmd/run_ee.go b/ee/acl/cmd/run_ee.go index 21f8536ec53..59b42bc9073 100644 --- a/ee/acl/cmd/run_ee.go +++ b/ee/acl/cmd/run_ee.go @@ -13,8 +13,12 @@ package acl import ( + "context" + "encoding/json" + "fmt" "os" "strings" + "time" "github.com/dgraph-io/dgo" "github.com/dgraph-io/dgo/protos/api" @@ -169,8 +173,24 @@ func initSubcommands() []*x.SubCommand { " are to be changed") chModFlags.IntP("perm", "P", 0, "The acl represented using "+ "an integer, 4 for read-only, 2 for write-only, and 1 for modify-only") + + var cmdInfo x.SubCommand + cmdInfo.Cmd = &cobra.Command{ + Use: "info", + Short: "Show info about a user or group", + Run: func(cmd *cobra.Command, args []string) { + if err := info(cmdInfo.Conf); err != nil { + glog.Errorf("Unable to show info:%v", err) + os.Exit(1) + } + }, + } + infoFlags := cmdInfo.Cmd.Flags() + infoFlags.StringP("user", "u", "", "The user to be shown") + infoFlags.StringP("group", "g", "", "The group to be shown") return []*x.SubCommand{ - &cmdUserAdd, &cmdUserDel, &cmdLogIn, &cmdGroupAdd, &cmdGroupDel, &cmdUserMod, &cmdChMod, + &cmdUserAdd, &cmdUserDel, &cmdLogIn, &cmdGroupAdd, &cmdGroupDel, &cmdUserMod, + &cmdChMod, &cmdInfo, } } @@ -199,3 +219,69 @@ func getDgraphClient(conf *viper.Viper) *dgo.Dgraph { return dgo.NewDgraphClient(clients...) } + +func info(conf *viper.Viper) error { + userId := conf.GetString("user") + groupId := conf.GetString("group") + if (len(userId) == 0 && len(groupId) == 0) || + (len(userId) != 0 && len(groupId) != 0) { + return fmt.Errorf("either the user or group should be specified, not both") + } + + dc := getDgraphClient(conf) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + txn := dc.NewTxn() + defer txn.Discard(ctx) + + if len(userId) != 0 { + user, err := queryUser(ctx, txn, userId) + if err != nil { + return err + } + + var userSB strings.Builder + userSB.WriteString(fmt.Sprintf("user %v:\n", userId)) + userSB.WriteString(fmt.Sprintf("uid:%v\nid:%v\n", user.Uid, user.UserID)) + var groupNames []string + for _, group := range user.Groups { + groupNames = append(groupNames, group.GroupID) + } + userSB.WriteString(fmt.Sprintf("groups:%v\n", strings.Join(groupNames, " "))) + glog.Infof(userSB.String()) + } + + if len(groupId) != 0 { + group, err := queryGroup(ctx, txn, groupId, "dgraph.xid", "~dgraph.user.group{dgraph.xid}", + "dgraph.group.acl") + if err != nil { + return err + } + // build the info string for group + var groupSB strings.Builder + groupSB.WriteString(fmt.Sprintf("group %v:\n", groupId)) + groupSB.WriteString(fmt.Sprintf("uid:%v\nid:%v\n", group.Uid, group.GroupID)) + + var userNames []string + for _, user := range group.Users { + userNames = append(userNames, user.UserID) + } + groupSB.WriteString(fmt.Sprintf("users:%v\n", strings.Join(userNames, " "))) + + var aclStrs []string + var acls []Acl + if err := json.Unmarshal([]byte(group.Acls), &acls); err != nil { + return fmt.Errorf("unable to unmarshal the acls associated with the group %v:%v", + groupId, err) + } + + for _, acl := range acls { + aclStrs = append(aclStrs, fmt.Sprintf("(predicate:%v,perm:%v)", acl.Predicate, acl.Perm)) + } + groupSB.WriteString(fmt.Sprintf("acls:%v\n", strings.Join(aclStrs, " "))) + + glog.Infof(groupSB.String()) + } + + return nil +} diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 465158c4439..433c2339740 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -148,6 +148,7 @@ func queryUser(ctx context.Context, txn *dgo.Txn, userid string) (user *acl.User query search($userid: string){ user(func: eq(dgraph.xid, $userid)) { uid + dgraph.xid dgraph.user.group { uid dgraph.xid diff --git a/ee/acl/utils.go b/ee/acl/utils.go index 52ed0dbd819..79ef74d8a8f 100644 --- a/ee/acl/utils.go +++ b/ee/acl/utils.go @@ -80,14 +80,15 @@ func UnmarshalUser(resp *api.Response, userKey string) (user *User, err error) { type Group struct { Uid string `json:"uid"` GroupID string `json:"dgraph.xid"` + Users []User `json:"~dgraph.user.group"` Acls string `json:"dgraph.group.acl"` } // Extract the first User pointed by the userKey in the query response -func UnmarshalGroup(resp *api.Response, groupKey string) (group *Group, err error) { +func UnmarshalGroup(input []byte, groupKey string) (group *Group, err error) { m := make(map[string][]Group) - err = json.Unmarshal(resp.GetJson(), &m) + err = json.Unmarshal(input, &m) if err != nil { glog.Errorf("Unable to unmarshal the query group response:%v", err) return nil, err @@ -98,7 +99,7 @@ func UnmarshalGroup(resp *api.Response, groupKey string) (group *Group, err erro return nil, nil } if len(groups) > 1 { - return nil, x.Errorf("Found multiple groups: %s", resp.GetJson()) + return nil, x.Errorf("Found multiple groups: %s", input) } return &groups[0], nil } From ca088ba9fbcdddf550bc31a4159208545e84da5c Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Tue, 4 Dec 2018 18:11:00 -0800 Subject: [PATCH 25/30] Added authentication using the refresh token --- dgraph/cmd/alpha/run.go | 8 +- edgraph/access_ee.go | 162 ++++++++++++++++++++++++++++++++++------ edgraph/config.go | 5 +- ee/acl/cmd/users.go | 3 +- ee/acl/utils.go | 21 +----- 5 files changed, 155 insertions(+), 44 deletions(-) diff --git a/dgraph/cmd/alpha/run.go b/dgraph/cmd/alpha/run.go index 839e3d0bb8f..f73d6a8185c 100644 --- a/dgraph/cmd/alpha/run.go +++ b/dgraph/cmd/alpha/run.go @@ -128,7 +128,10 @@ they form a Raft group and provide synchronous replication. " For Grpc, in auth-token key in the context.") flag.String("hmac_secret_file", "", "The file storing the HMAC secret"+ " that is used for signing the JWT. Enterprise feature.") - flag.Duration("jwt_ttl", 6*time.Hour, "The TTL of jwt tokens. Enterprise feature.") + flag.Duration("access_jwt_ttl", 6*time.Hour, "The TTL for the access jwt. "+ + "Enterprise feature.") + flag.Duration("refresh_jwt_ttl", 30*24*time.Hour, "The TTL for the refresh jwt. "+ + "Enterprise feature.") flag.Float64P("lru_mb", "l", -1, "Estimated memory the LRU cache can take. "+ "Actual usage by the process would be more than specified here.") @@ -409,7 +412,8 @@ func run() { } opts.HmacSecret = hmacSecret - opts.JwtTtl = Alpha.Conf.GetDuration("jwt_ttl") + opts.AccessJwtTtl = Alpha.Conf.GetDuration("access_jwt_ttl") + opts.RefreshJwtTtl = Alpha.Conf.GetDuration("refresh_jwt_ttl") glog.Info("HMAC secret loaded successfully.") } diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go index f2f35d49f4f..d830f053f94 100644 --- a/edgraph/access_ee.go +++ b/edgraph/access_ee.go @@ -21,7 +21,7 @@ import ( "github.com/dgraph-io/dgo/protos/api" "github.com/dgraph-io/dgraph/ee/acl" - jwt "github.com/dgrijalva/jwt-go" + "github.com/dgrijalva/jwt-go" "github.com/golang/glog" "google.golang.org/grpc/peer" @@ -43,45 +43,165 @@ func (s *Server) Login(ctx context.Context, }, "client ip for login") } - if err := acl.ValidateLoginRequest(request); err != nil { - glog.Warningf("Invalid login from: %s", addr) - return nil, err + user, err := s.authenticate(ctx, request) + if err != nil { + errMsg := fmt.Sprintf("authentication from address %s failed: %v", addr, err) + glog.Errorf(errMsg) + return nil, fmt.Errorf(errMsg) } resp := &api.Response{} - - user, err := s.queryUser(ctx, request.Userid, request.Password) + accessJwt, err := getAccessJwt(request.Userid, user.Groups) if err != nil { - glog.Warningf("Unable to login user id: %v. Addr: %s", request.Userid, addr) - return nil, err + errMsg := fmt.Sprintf("unable to get access jwt (userid=%s,addr=%s):%v", + request.Userid, addr, err) + glog.Errorf(errMsg) + return nil, fmt.Errorf(errMsg) } - if user == nil { - errMsg := fmt.Sprintf("User not found for user id %v. Addr: %s", request.Userid, addr) - glog.Warningf(errMsg) + refreshJwt, err := getRefreshJwt(request.Userid) + if err != nil { + errMsg := fmt.Sprintf("unable to get refresh jwt (userid=%s,addr=%s):%v", + request.Userid, addr, err) + glog.Errorf(errMsg) return nil, fmt.Errorf(errMsg) } - if !user.PasswordMatch { - errMsg := fmt.Sprintf("Password mismatch for user: %v. Addr: %s", request.Userid, addr) - glog.Warningf(errMsg) + + loginJwt := api.Jwt{ + AccessJwt: accessJwt, + RefreshJwt: refreshJwt, + } + + jwtBytes, err := loginJwt.Marshal() + if err != nil { + errMsg := fmt.Sprintf("unable to marshal jwt (userid=%s,addr=%s):%v", + request.Userid, addr, err) + glog.Errorf(errMsg) return nil, fmt.Errorf(errMsg) } + resp.Json = jwtBytes + return resp, nil +} + +func (s *Server) authenticate(ctx context.Context, request *api.LogInRequest) (*acl.User, error) { + if err := validateLoginRequest(request); err != nil { + return nil, fmt.Errorf("invalid login request: %v", err) + } + + var user *acl.User + if len(request.RefreshToken) > 0 { + userId, err := authenticateRefreshToken(request.RefreshToken) + if err != nil { + return nil, fmt.Errorf("unable to authenticate the refresh token %v: %v", + request.RefreshToken, err) + } + + user, err = s.queryUser(ctx, userId, "") + if err != nil { + return nil, fmt.Errorf("error while querying user with id: %v", + request.Userid) + } + + if user == nil { + return nil, fmt.Errorf("user not found for id %v", request.Userid) + } + } else { + var err error + user, err = s.queryUser(ctx, request.Userid, request.Password) + if err != nil { + return nil, fmt.Errorf("error while querying user with id: %v", + request.Userid) + } + + if user == nil { + return nil, fmt.Errorf("user not found for id %v", request.Userid) + } + if !user.PasswordMatch { + return nil, fmt.Errorf("password mismatch for user: %v", request.Userid) + } + } + + return user, nil +} + +func authenticateRefreshToken(refreshToken string) (string, error) { + token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return Config.HmacSecret, nil + }) + + if err != nil { + return "", fmt.Errorf("unable to parse refresh token:%v", err) + } + claims, ok := token.Claims.(jwt.MapClaims) + if (!ok) || (!token.Valid) { + return "", fmt.Errorf("claims in refresh token is not map claims:%v", refreshToken) + } + + // by default, the MapClaims.Valid will return true if the exp field is not set + // here we enforce the checking to make sure that the refresh token has not expired + now := time.Now().Unix() + if claims.VerifyExpiresAt(now, true) == false { + return "", fmt.Errorf("refresh token has expired: %v", refreshToken) + } + + userId, ok := claims["userid"].(string) + if !ok { + return "", fmt.Errorf("userid in claims is not a string:%v", userId) + } + return userId, nil +} + +func validateLoginRequest(request *api.LogInRequest) error { + if request == nil { + return fmt.Errorf("the request should not be nil") + } + // we will use the refresh token for authentication if it's set + if len(request.RefreshToken) > 0 { + return nil + } + + // otherwise make sure both userid and password are set + if len(request.Userid) == 0 { + return fmt.Errorf("the userid should not be empty") + } + if len(request.Password) == 0 { + return fmt.Errorf("the password should not be empty") + } + return nil +} + +func getAccessJwt(userId string, groups []acl.Group) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "userid": request.Userid, - "groups": acl.ToJwtGroups(user.Groups), + "userid": userId, + "groups": acl.GetGroupIDs(groups), // set the jwt exp according to the ttl "exp": json.Number( - strconv.FormatInt(time.Now().Add(Config.JwtTtl).Unix(), 10)), + strconv.FormatInt(time.Now().Add(Config.AccessJwtTtl).Unix(), 10)), }) jwtString, err := token.SignedString(Config.HmacSecret) if err != nil { - glog.Errorf("Unable to encode jwt to string: %v", err) - return nil, err + return "", fmt.Errorf("unable to encode jwt to string: %v", err) } + return jwtString, nil +} - resp.Json = []byte(jwtString) - return resp, nil +func getRefreshJwt(userId string) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "userid": userId, + // set the jwt exp according to the ttl + "exp": json.Number( + strconv.FormatInt(time.Now().Add(Config.RefreshJwtTtl).Unix(), 10)), + }) + + jwtString, err := token.SignedString(Config.HmacSecret) + if err != nil { + return "", fmt.Errorf("unable to encode jwt to string: %v", err) + } + return jwtString, nil } const queryUser = ` diff --git a/edgraph/config.go b/edgraph/config.go index 7d7096e78fe..6ba4e9ad7de 100644 --- a/edgraph/config.go +++ b/edgraph/config.go @@ -36,8 +36,9 @@ type Options struct { AllottedMemory float64 - HmacSecret []byte - JwtTtl time.Duration + HmacSecret []byte + AccessJwtTtl time.Duration + RefreshJwtTtl time.Duration } var Config Options diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 433c2339740..a943f483f0f 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -139,7 +139,8 @@ func userLogin(conf *viper.Viper) error { return fmt.Errorf("unable to login:%v", err) } updatedContext := dc.GetContext(ctx) - glog.Infof("Login successfully with jwt:\n%v", updatedContext.Value("jwt")) + glog.Infof("Login successfully.\naccess jwt:\n%v\nrefresh jwt:\n%v", + updatedContext.Value("accessJwt"), updatedContext.Value("refreshJwt")) return nil } diff --git a/ee/acl/utils.go b/ee/acl/utils.go index 79ef74d8a8f..173ab28e673 100644 --- a/ee/acl/utils.go +++ b/ee/acl/utils.go @@ -21,30 +21,15 @@ import ( "github.com/golang/glog" ) -func ValidateLoginRequest(request *api.LogInRequest) error { - if request == nil { - return fmt.Errorf("the request should not be nil") - } - if len(request.Userid) == 0 { - return fmt.Errorf("the userid should not be empty") - } - if len(request.Password) == 0 { - return fmt.Errorf("the password should not be empty") - } - return nil -} - -func ToJwtGroups(groups []Group) []JwtGroup { +func GetGroupIDs(groups []Group) []string { if len(groups) == 0 { // the user does not have any groups return nil } - jwtGroups := make([]JwtGroup, 0, len(groups)) + jwtGroups := make([]string, 0, len(groups)) for _, g := range groups { - jwtGroups = append(jwtGroups, JwtGroup{ - Group: g.GroupID, - }) + jwtGroups = append(jwtGroups, g.GroupID) } return jwtGroups } From c33e300677f0d7365fb3a3df951461abeea689d8 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Wed, 5 Dec 2018 14:56:06 -0800 Subject: [PATCH 26/30] Addressing comments --- edgraph/access_ee.go | 12 ++++++------ ee/acl/cmd/groups.go | 9 ++++++--- ee/acl/cmd/run_ee.go | 33 +++++++++++++++++---------------- ee/acl/cmd/users.go | 12 ++++++++---- x/x.go | 3 +-- 5 files changed, 38 insertions(+), 31 deletions(-) diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go index d830f053f94..c3a6e8e7e9b 100644 --- a/edgraph/access_ee.go +++ b/edgraph/access_ee.go @@ -29,8 +29,8 @@ import ( ) func (s *Server) Login(ctx context.Context, - request *api.LogInRequest) (*api.Response, error) { - ctx, span := otrace.StartSpan(ctx, "server.LogIn") + request *api.LoginRequest) (*api.Response, error) { + ctx, span := otrace.StartSpan(ctx, "server.Login") defer span.End() // record the client ip for this login request @@ -82,7 +82,7 @@ func (s *Server) Login(ctx context.Context, return resp, nil } -func (s *Server) authenticate(ctx context.Context, request *api.LogInRequest) (*acl.User, error) { +func (s *Server) authenticate(ctx context.Context, request *api.LoginRequest) (*acl.User, error) { if err := validateLoginRequest(request); err != nil { return nil, fmt.Errorf("invalid login request: %v", err) } @@ -136,14 +136,14 @@ func authenticateRefreshToken(refreshToken string) (string, error) { } claims, ok := token.Claims.(jwt.MapClaims) - if (!ok) || (!token.Valid) { + if !ok || !token.Valid { return "", fmt.Errorf("claims in refresh token is not map claims:%v", refreshToken) } // by default, the MapClaims.Valid will return true if the exp field is not set // here we enforce the checking to make sure that the refresh token has not expired now := time.Now().Unix() - if claims.VerifyExpiresAt(now, true) == false { + if !claims.VerifyExpiresAt(now, true) { return "", fmt.Errorf("refresh token has expired: %v", refreshToken) } @@ -154,7 +154,7 @@ func authenticateRefreshToken(refreshToken string) (string, error) { return userId, nil } -func validateLoginRequest(request *api.LogInRequest) error { +func validateLoginRequest(request *api.LoginRequest) error { if request == nil { return fmt.Errorf("the request should not be nil") } diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index 837592c0834..c172b9a206a 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -32,7 +32,8 @@ func groupAdd(conf *viper.Viper) error { return fmt.Errorf("the group id should not be empty") } - dc := getDgraphClient(conf) + dc, close := getDgraphClient(conf) + defer close() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() @@ -72,7 +73,8 @@ func groupDel(conf *viper.Viper) error { return fmt.Errorf("the group id should not be empty") } - dc := getDgraphClient(conf) + dc, close := getDgraphClient(conf) + defer close() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() @@ -146,7 +148,8 @@ func chMod(conf *viper.Viper) error { return fmt.Errorf("the predicate must not be empty") } - dc := getDgraphClient(conf) + dc, close := getDgraphClient(conf) + defer close() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() diff --git a/ee/acl/cmd/run_ee.go b/ee/acl/cmd/run_ee.go index 59b42bc9073..32c4a286f49 100644 --- a/ee/acl/cmd/run_ee.go +++ b/ee/acl/cmd/run_ee.go @@ -194,7 +194,9 @@ func initSubcommands() []*x.SubCommand { } } -func getDgraphClient(conf *viper.Viper) *dgo.Dgraph { +type CloseFunc func() + +func getDgraphClient(conf *viper.Viper) (*dgo.Dgraph, CloseFunc) { opt = options{ dgraph: conf.GetString("dgraph"), } @@ -207,17 +209,15 @@ func getDgraphClient(conf *viper.Viper) *dgo.Dgraph { x.LoadTLSConfig(&tlsConf, CmdAcl.Conf, x.TlsClientCert, x.TlsClientKey) tlsConf.ServerName = CmdAcl.Conf.GetString("tls_server_name") - ds := strings.Split(opt.dgraph, ",") - var clients []api.DgraphClient - for _, d := range ds { - conn, err := x.SetupConnection(d, &tlsConf) - x.Checkf(err, "While trying to setup connection to Dgraph alpha.") + conn, err := x.SetupConnection(opt.dgraph, &tlsConf) + x.Checkf(err, "While trying to setup connection to Dgraph alpha.") - dc := api.NewDgraphClient(conn) - clients = append(clients, dc) + dc := api.NewDgraphClient(conn) + return dgo.NewDgraphClient(dc), func() { + if err := conn.Close(); err != nil { + glog.Errorf("Error while closing connection:%v", err) + } } - - return dgo.NewDgraphClient(clients...) } func info(conf *viper.Viper) error { @@ -228,7 +228,8 @@ func info(conf *viper.Viper) error { return fmt.Errorf("either the user or group should be specified, not both") } - dc := getDgraphClient(conf) + dc, close := getDgraphClient(conf) + defer close() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() @@ -240,15 +241,15 @@ func info(conf *viper.Viper) error { return err } - var userSB strings.Builder - userSB.WriteString(fmt.Sprintf("user %v:\n", userId)) - userSB.WriteString(fmt.Sprintf("uid:%v\nid:%v\n", user.Uid, user.UserID)) + var userBuf strings.Builder + userBuf.WriteString(fmt.Sprintf("user %v:\n", userId)) + userBuf.WriteString(fmt.Sprintf("uid:%v\nid:%v\n", user.Uid, user.UserID)) var groupNames []string for _, group := range user.Groups { groupNames = append(groupNames, group.GroupID) } - userSB.WriteString(fmt.Sprintf("groups:%v\n", strings.Join(groupNames, " "))) - glog.Infof(userSB.String()) + userBuf.WriteString(fmt.Sprintf("groups:%v\n", strings.Join(groupNames, " "))) + glog.Infof(userBuf.String()) } if len(groupId) != 0 { diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index a943f483f0f..15c06d0b912 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -37,7 +37,8 @@ func userAdd(conf *viper.Viper) error { return fmt.Errorf("the password must not be empty") } - dc := getDgraphClient(conf) + dc, close := getDgraphClient(conf) + defer close() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() @@ -83,7 +84,8 @@ func userDel(conf *viper.Viper) error { return fmt.Errorf("the user id should not be empty") } - dc := getDgraphClient(conf) + dc, close := getDgraphClient(conf) + defer close() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() @@ -129,7 +131,8 @@ func userLogin(conf *viper.Viper) error { return fmt.Errorf("the password must not be empty") } - dc := getDgraphClient(conf) + dc, close := getDgraphClient(conf) + defer close() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() @@ -178,7 +181,8 @@ func userMod(conf *viper.Viper) error { return fmt.Errorf("the user must not be empty") } - dc := getDgraphClient(conf) + dc, close := getDgraphClient(conf) + defer close() ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() diff --git a/x/x.go b/x/x.go index 3548b29c8b1..27bfca7a924 100644 --- a/x/x.go +++ b/x/x.go @@ -444,8 +444,7 @@ func SetupConnection(host string, tlsConf *TLSHelperConfig) (*grpc.ClientConn, e return grpc.Dial(host, opts...) } -func Diff(targetMap map[string]struct{}, existingMap map[string]struct{}) ([]string, - []string) { +func Diff(targetMap map[string]struct{}, existingMap map[string]struct{}) ([]string, []string) { var newGroups []string var groupsToBeDeleted []string From 56745bc14edb30b10983bae116f05199d2f3caf5 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 6 Dec 2018 13:08:40 -0800 Subject: [PATCH 27/30] Added test for updateAcl --- ee/acl/cmd/groups_test.go | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 ee/acl/cmd/groups_test.go diff --git a/ee/acl/cmd/groups_test.go b/ee/acl/cmd/groups_test.go new file mode 100644 index 00000000000..c4b7592d38a --- /dev/null +++ b/ee/acl/cmd/groups_test.go @@ -0,0 +1,63 @@ +// +build !oss + +/* + * Copyright 2018 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Dgraph Community License (the "License"); you + * may not use this file except in compliance with the License. You + * may obtain a copy of the License at + * + * https://github.com/dgraph-io/dgraph/blob/master/licenses/DCL.txt + */ +package acl + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUpdateAcl(t *testing.T) { + var currenAcls []Acl + newAcl := Acl{ + Predicate: "friend", + Perm: 4, + } + updatedAcls1, changed := updateAcl(currenAcls, newAcl) + require.True(t, changed, "the acl list should be changed") + require.Equal(t, 1, len(updatedAcls1), "the updated acl list should have 1 element") + + // trying to update the acl list again with the exactly same acl won't change it + updatedAcls2, changed := updateAcl(updatedAcls1, newAcl) + require.False(t, changed, "the acl list should not be changed through update with "+ + "an existing element") + require.Equal(t, 1, len(updatedAcls2), "the updated acl list should still have 1 element") + require.Equal(t, int32(4), updatedAcls2[0].Perm, "the perm should still have the value of 4") + + newAcl.Perm = 6 + updatedAcls3, changed := updateAcl(updatedAcls1, newAcl) + require.True(t, changed, "the acl list should be changed through update "+ + "with element of new perm") + require.Equal(t, 1, len(updatedAcls3), "the updated acl list should still have 1 element") + require.Equal(t, int32(6), updatedAcls3[0].Perm, "the updated perm should be 6 now") + + newAcl = Acl{ + Predicate: "buddy", + Perm: 6, + } + updatedAcls4, changed := updateAcl(updatedAcls3, newAcl) + require.True(t, changed, "the acl should be changed through update "+ + "with element of new predicate") + require.Equal(t, 2, len(updatedAcls4), "the acl list should have 2 elements now") + + newAcl = Acl{ + Predicate: "buddy", + Perm: -3, + } + updatedAcls5, changed := updateAcl(updatedAcls4, newAcl) + require.True(t, changed, "the acl should be changed through update "+ + "with element of negative predicate") + require.Equal(t, 1, len(updatedAcls5), "the acl list should have 1 element now") + require.Equal(t, "friend", updatedAcls5[0].Predicate, "the left acl should have the original "+ + "first predicate") +} From e6a5f44a406feca984b50112fd62f2e523f8ea34 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 6 Dec 2018 13:26:21 -0800 Subject: [PATCH 28/30] fixing issues for make oss --- edgraph/access.go | 2 +- ee/acl/cmd/run.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 ee/acl/cmd/run.go diff --git a/edgraph/access.go b/edgraph/access.go index f0832c6d31a..f347f489294 100644 --- a/edgraph/access.go +++ b/edgraph/access.go @@ -27,7 +27,7 @@ import ( ) func (s *Server) Login(ctx context.Context, - request *api.LogInRequest) (*api.Response, error) { + request *api.LoginRequest) (*api.Response, error) { glog.Warningf("Login failed: %s", x.ErrNotSupported) return &api.Response{}, x.ErrNotSupported diff --git a/ee/acl/cmd/run.go b/ee/acl/cmd/run.go new file mode 100644 index 00000000000..efb614b9b72 --- /dev/null +++ b/ee/acl/cmd/run.go @@ -0,0 +1,33 @@ +// +build oss + +/* + * Copyright 2018 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package acl + +import ( + "github.com/dgraph-io/dgraph/x" + "github.com/spf13/cobra" +) + +var CmdAcl x.SubCommand + +func init() { + CmdAcl.Cmd = &cobra.Command{ + Use: "acl", + Short: "Enterprise feature. Not supported in oss version", + } +} From 375e581fac634382a1f22fc522a934f2e59e49f7 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 6 Dec 2018 13:41:05 -0800 Subject: [PATCH 29/30] making the golangcibot happy --- ee/acl/cmd/groups.go | 18 +++++++++++++++--- ee/acl/cmd/run_ee.go | 8 ++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/ee/acl/cmd/groups.go b/ee/acl/cmd/groups.go index c172b9a206a..ea6b9ab21b9 100644 --- a/ee/acl/cmd/groups.go +++ b/ee/acl/cmd/groups.go @@ -37,7 +37,11 @@ func groupAdd(conf *viper.Viper) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() - defer txn.Discard(ctx) + defer func() { + if err := txn.Discard(ctx); err != nil { + glog.Errorf("Unable to discard transaction:%v", err) + } + }() group, err := queryGroup(ctx, txn, groupId) if err != nil { @@ -78,7 +82,11 @@ func groupDel(conf *viper.Viper) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() - defer txn.Discard(ctx) + defer func() { + if err := txn.Discard(ctx); err != nil { + glog.Errorf("Unable to discard transaction:%v", err) + } + }() group, err := queryGroup(ctx, txn, groupId) if err != nil { @@ -153,7 +161,11 @@ func chMod(conf *viper.Viper) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() - defer txn.Discard(ctx) + defer func() { + if err := txn.Discard(ctx); err != nil { + glog.Errorf("Unable to discard transaction:%v", err) + } + }() group, err := queryGroup(ctx, txn, groupId, "dgraph.group.acl") if err != nil { diff --git a/ee/acl/cmd/run_ee.go b/ee/acl/cmd/run_ee.go index 32c4a286f49..5966c0d540c 100644 --- a/ee/acl/cmd/run_ee.go +++ b/ee/acl/cmd/run_ee.go @@ -54,8 +54,12 @@ func init() { for _, sc := range subcommands { CmdAcl.Cmd.AddCommand(sc.Cmd) sc.Conf = viper.New() - sc.Conf.BindPFlags(sc.Cmd.Flags()) - sc.Conf.BindPFlags(CmdAcl.Cmd.PersistentFlags()) + if err := sc.Conf.BindPFlags(sc.Cmd.Flags()); err != nil { + glog.Fatalf("Unable to bind flags for command %v:%v", sc, err) + } + if err := sc.Conf.BindPFlags(CmdAcl.Cmd.PersistentFlags()); err != nil { + glog.Fatalf("Unable to bind persistent flags from acl for command %v:%v", sc, err) + } sc.Conf.SetEnvPrefix(sc.EnvPrefix) } } From 7916fccf513b2b72de63f6b04a82ef65f67b95d4 Mon Sep 17 00:00:00 2001 From: Lucas Wang Date: Thu, 6 Dec 2018 13:49:23 -0800 Subject: [PATCH 30/30] make the golangcibot happy --- ee/acl/cmd/run_ee.go | 6 +++++- ee/acl/cmd/users.go | 24 ++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/ee/acl/cmd/run_ee.go b/ee/acl/cmd/run_ee.go index 5966c0d540c..203e96ff949 100644 --- a/ee/acl/cmd/run_ee.go +++ b/ee/acl/cmd/run_ee.go @@ -237,7 +237,11 @@ func info(conf *viper.Viper) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() - defer txn.Discard(ctx) + defer func() { + if err := txn.Discard(ctx); err != nil { + glog.Errorf("Unable to discard transaction:%v", err) + } + }() if len(userId) != 0 { user, err := queryUser(ctx, txn, userId) diff --git a/ee/acl/cmd/users.go b/ee/acl/cmd/users.go index 15c06d0b912..b71c72cdc8b 100644 --- a/ee/acl/cmd/users.go +++ b/ee/acl/cmd/users.go @@ -42,7 +42,11 @@ func userAdd(conf *viper.Viper) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() - defer txn.Discard(ctx) + defer func() { + if err := txn.Discard(ctx); err != nil { + glog.Errorf("Unable to discard transaction:%v", err) + } + }() user, err := queryUser(ctx, txn, userid) if err != nil { @@ -89,7 +93,11 @@ func userDel(conf *viper.Viper) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() - defer txn.Discard(ctx) + defer func() { + if err := txn.Discard(ctx); err != nil { + glog.Errorf("Unable to discard transaction:%v", err) + } + }() user, err := queryUser(ctx, txn, userid) if err != nil { @@ -136,7 +144,11 @@ func userLogin(conf *viper.Viper) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() - defer txn.Discard(ctx) + defer func() { + if err := txn.Discard(ctx); err != nil { + glog.Errorf("Unable to discard transaction:%v", err) + } + }() if err := dc.Login(ctx, userid, password); err != nil { return fmt.Errorf("unable to login:%v", err) @@ -186,7 +198,11 @@ func userMod(conf *viper.Viper) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() txn := dc.NewTxn() - defer txn.Discard(ctx) + defer func() { + if err := txn.Discard(ctx); err != nil { + glog.Errorf("Unable to discard transaction:%v", err) + } + }() user, err := queryUser(ctx, txn, userId) if err != nil {