diff --git a/edgraph/access.go b/edgraph/access.go index 07098b347a5..345328dadec 100644 --- a/edgraph/access.go +++ b/edgraph/access.go @@ -77,6 +77,7 @@ func AuthorizeGuardians(ctx context.Context) error { } func AuthGuardianOfTheGalaxy(ctx context.Context) error { + // always allow access return nil } diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go index 64285e07711..22ab0f7f894 100644 --- a/edgraph/access_ee.go +++ b/edgraph/access_ee.go @@ -1039,6 +1039,9 @@ func authorizeSchemaQuery(ctx context.Context, er *query.ExecutionResult) error return nil } +// AuthGuardianOfTheGalaxy authorizes the operations for the users who belong to the guardians +// group in the galaxy namespace. This authorization is used for admin usages like creation and +// deletion of a namespace, resetting passwords across namespaces etc. func AuthGuardianOfTheGalaxy(ctx context.Context) error { if !x.WorkerConfig.AclEnabled { return nil diff --git a/edgraph/multi_tenancy.go b/edgraph/multi_tenancy.go new file mode 100644 index 00000000000..343bdf72f08 --- /dev/null +++ b/edgraph/multi_tenancy.go @@ -0,0 +1,43 @@ +// +build oss + +/* + * Copyright 2021 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 edgraph + +import "context" + +type ResetPasswordInput struct { + UserID string + Password string + Namespace uint64 +} + +func (s *Server) CreateNamespace(ctx context.Context) (uint64, error) { + return 0, nil +} + +func (s *Server) DeleteNamespace(ctx context.Context, namespace uint64) error { + return nil +} + +func (s *Server) ResetPassword(ctx context.Context, ns *ResetPasswordInput) error { + return nil +} + +func createGuardianAndGroot(ctx context.Context, namespace uint64) error { + return nil +} diff --git a/edgraph/multi_tenancy_ee.go b/edgraph/multi_tenancy_ee.go new file mode 100644 index 00000000000..cf70f304f32 --- /dev/null +++ b/edgraph/multi_tenancy_ee.go @@ -0,0 +1,151 @@ +// +build !oss + +/* + * Copyright 2021 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" + "encoding/json" + "fmt" + + "github.com/dgraph-io/dgo/v200/protos/api" + "github.com/dgraph-io/dgraph/protos/pb" + "github.com/dgraph-io/dgraph/query" + "github.com/dgraph-io/dgraph/schema" + "github.com/dgraph-io/dgraph/worker" + "github.com/dgraph-io/dgraph/x" + "github.com/golang/glog" + "github.com/pkg/errors" +) + +type ResetPasswordInput struct { + UserID string + Password string + Namespace uint64 +} + +func (s *Server) ResetPassword(ctx context.Context, inp *ResetPasswordInput) error { + if err := AuthGuardianOfTheGalaxy(ctx); err != nil { + return errors.Wrapf(err, "Reset password got error:") + } + + query := fmt.Sprintf(`{ + x as updateUser(func: eq(dgraph.xid, "%s")) @filter(type(dgraph.type.User)) { + uid + } + }`, inp.UserID) + + userNQuads := []*api.NQuad{ + { + Subject: "uid(x)", + Predicate: "dgraph.password", + ObjectValue: &api.Value{Val: &api.Value_StrVal{StrVal: inp.Password}}, + }, + } + req := &Request{ + req: &api.Request{ + CommitNow: true, + Query: query, + Mutations: []*api.Mutation{ + { + Set: userNQuads, + Cond: "@if(gt(len(x), 0))", + }, + }, + }, + doAuth: NoAuthorize, + } + ctx = x.AttachNamespace(ctx, inp.Namespace) + resp, err := (&Server{}).doQuery(ctx, req) + if err != nil { + return errors.Wrapf(err, "Reset password for user %s in namespace %d, got error:", + inp.UserID, inp.Namespace) + } + + type userNode struct { + Uid string `json:"uid"` + } + + type userQryResp struct { + User []userNode `json:"updateUser"` + } + var userResp userQryResp + if err := json.Unmarshal(resp.GetJson(), &userResp); err != nil { + return errors.Wrap(err, "Reset password failed with error") + } + + if len(userResp.User) == 0 { + return errors.New("Failed to reset password, user doesn't exist") + } + return nil +} + +func (s *Server) CreateNamespace(ctx context.Context) (uint64, error) { + ctx = x.AttachJWTNamespace(ctx) + glog.V(2).Info("Got create namespace request from namespace: ", x.ExtractNamespace(ctx)) + + // Namespace creation is only allowed by the guardians of the galaxy group. + if err := AuthGuardianOfTheGalaxy(ctx); err != nil { + return 0, errors.Wrapf(err, "Creating namespace, got error:") + } + + num := &pb.Num{Val: 1, Type: pb.Num_NS_ID} + ids, err := worker.AssignNsIdsOverNetwork(ctx, num) + if err != nil { + return 0, errors.Wrapf(err, "Creating namespace, got error:") + } + + ns := ids.StartId + glog.V(2).Infof("Got a lease for NsID: %d", ns) + + // Attach the newly leased NsID in the context in order to create guardians/groot for it. + ctx = x.AttachNamespace(ctx, ns) + m := &pb.Mutations{StartTs: worker.State.GetTimestamp(false)} + m.Schema = schema.InitialSchema(ns) + m.Types = schema.InitialTypes(ns) + _, err = query.ApplyMutations(ctx, m) + if err != nil { + return 0, err + } + + if err = worker.WaitForIndexing(ctx, true); err != nil { + return 0, errors.Wrap(err, "Creating namespace, got error: ") + } + if err := createGuardianAndGroot(ctx, ids.StartId); err != nil { + return 0, errors.Wrapf(err, "Failed to create guardian and groot: ") + } + glog.V(2).Infof("Created namespace: %d", ns) + return ns, nil +} + +// This function is used while creating new namespace. New namespace creation is only allowed +// by the guardians of the galaxy group. +func createGuardianAndGroot(ctx context.Context, namespace uint64) error { + if err := upsertGuardian(ctx); err != nil { + return errors.Wrap(err, "While creating Guardian") + } + if err := upsertGroot(ctx); err != nil { + return errors.Wrap(err, "While creating Groot") + } + return nil +} + +func (s *Server) DeleteNamespace(ctx context.Context, namespace uint64) error { + glog.Info("Deleting namespace", namespace) + ctx = x.AttachJWTNamespace(ctx) + if err := AuthGuardianOfTheGalaxy(ctx); err != nil { + return errors.Wrapf(err, "Creating namespace, got error: ") + } + // TODO(Ahsan): We have to ban the pstore for all the groups. + ps := worker.State.Pstore + return ps.BanNamespace(namespace) +} diff --git a/edgraph/server.go b/edgraph/server.go index 4f021780133..1619d44a639 100644 --- a/edgraph/server.go +++ b/edgraph/server.go @@ -109,65 +109,6 @@ type existingGQLSchemaQryResp struct { ExistingGQLSchema []graphQLSchemaNode `json:"ExistingGQLSchema"` } -func (s *Server) CreateNamespace(ctx context.Context) (uint64, error) { - ctx = x.AttachJWTNamespace(ctx) - glog.V(2).Info("Got create namespace request from namespace: ", x.ExtractNamespace(ctx)) - - // Namespace creation is only allowed by the guardians of the galaxy group. - if err := AuthGuardianOfTheGalaxy(ctx); err != nil { - return 0, errors.Wrapf(err, "Creating namespace, got error:") - } - - num := &pb.Num{Val: 1, Type: pb.Num_NS_ID} - ids, err := worker.AssignNsIdsOverNetwork(ctx, num) - if err != nil { - return 0, errors.Wrapf(err, "Creating namespace, got error:") - } - - ns := ids.StartId - glog.V(2).Infof("Got a lease for NsID: %d", ns) - - // Attach the newly leased NsID in the context in order to create guardians/groot for it. - ctx = x.AttachNamespace(ctx, ns) - m := &pb.Mutations{StartTs: worker.State.GetTimestamp(false)} - m.Schema = schema.InitialSchema(ns) - m.Types = schema.InitialTypes(ns) - _, err = query.ApplyMutations(ctx, m) - if err != nil { - return 0, err - } - - if err = worker.WaitForIndexing(ctx, true); err != nil { - return 0, errors.Wrap(err, "Creating namespace, got error: ") - } - if err := createGuardianAndGroot(ctx, ids.StartId); err != nil { - return 0, errors.Wrapf(err, "Failed to create guardian and groot: ") - } - glog.V(2).Infof("Created namespace: %d", ns) - return ns, nil -} - -// This function is used while creating new namespace. New namespace creation is only allowed -// by the guardians of the galaxy group. -func createGuardianAndGroot(ctx context.Context, namespace uint64) error { - if err := upsertGuardian(ctx); err != nil { - return errors.Wrap(err, "While creating Guardian") - } - if err := upsertGroot(ctx); err != nil { - return errors.Wrap(err, "While creating Groot") - } - return nil -} - -func (s *Server) DeleteNamespace(ctx context.Context, namespace uint64) error { - glog.Info("Deleting namespace", namespace) - ctx = x.AttachJWTNamespace(ctx) - if err := AuthGuardianOfTheGalaxy(ctx); err != nil { - return errors.Wrapf(err, "Deleting namespace, got error: ") - } - return worker.ProcessDeleteNsRequest(ctx, namespace) -} - // PeriodicallyPostTelemetry periodically reports telemetry data for alpha. func PeriodicallyPostTelemetry() { glog.V(2).Infof("Starting telemetry data collection for alpha...") diff --git a/graphql/admin/admin.go b/graphql/admin/admin.go index 39db925b9b3..194daa1eda8 100644 --- a/graphql/admin/admin.go +++ b/graphql/admin/admin.go @@ -583,6 +583,7 @@ func newAdminResolverFactory() resolve.ResolverFactory { "draining": resolveDraining, "export": resolveExport, "login": resolveLogin, + "resetPassword": resolveResetPassword, "restore": resolveRestore, "shutdown": resolveShutdown, } diff --git a/graphql/admin/endpoints_ee.go b/graphql/admin/endpoints_ee.go index 3f5ded1cf07..673a60f30ff 100644 --- a/graphql/admin/endpoints_ee.go +++ b/graphql/admin/endpoints_ee.go @@ -422,6 +422,18 @@ const adminTypes = ` namespaceId: Int message: String } + + input ResetPasswordInput { + userId: String! + password: String! + namespace: Int! + } + + type ResetPasswordPayload { + userId: String + message: String + namespace: Int + } ` const adminMutations = ` @@ -483,6 +495,12 @@ const adminMutations = ` Delete a namespace. """ deleteNamespace(input: NamespaceInput!): NamespacePayload + + """ + Reset password can only be used by the Guardians of the galaxy to reset password of + any user in any namespace. + """ + resetPassword(input: ResetPasswordInput!): ResetPasswordPayload ` const adminQueries = ` diff --git a/graphql/admin/reset_password.go b/graphql/admin/reset_password.go new file mode 100644 index 00000000000..110ae46b081 --- /dev/null +++ b/graphql/admin/reset_password.go @@ -0,0 +1,68 @@ +/* + * Copyright 2020 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 admin + +import ( + "context" + "encoding/json" + "strconv" + + "github.com/dgraph-io/dgraph/edgraph" + "github.com/dgraph-io/dgraph/graphql/resolve" + "github.com/dgraph-io/dgraph/graphql/schema" + "github.com/golang/glog" +) + +func resolveResetPassword(ctx context.Context, m schema.Mutation) (*resolve.Resolved, bool) { + inp, err := getPasswordInput(m) + if err != nil { + glog.Error("Failed to parse the reset password input") + } + if err = (&edgraph.Server{}).ResetPassword(ctx, inp); err != nil { + return resolve.EmptyResult(m, err), false + } + + return resolve.DataResult( + m, + map[string]interface{}{ + m.Name(): map[string]interface{}{ + "userId": inp.UserID, + "message": "Reset password is successful", + "namespace": json.Number(strconv.Itoa(int(inp.Namespace))), + }, + }, + nil, + ), true + +} + +func getPasswordInput(m schema.Mutation) (*edgraph.ResetPasswordInput, error) { + var input edgraph.ResetPasswordInput + + inputArg := m.ArgValue(schema.InputArgName) + inputByts, err := json.Marshal(inputArg) + + if err != nil { + return nil, schema.GQLWrapf(err, "couldn't get input argument") + } + + if err := json.Unmarshal(inputByts, &input); err != nil { + return nil, schema.GQLWrapf(err, "couldn't get input argument") + } + + return &input, nil +}