diff --git a/dgraph/cmd/alpha/run.go b/dgraph/cmd/alpha/run.go index 77ea62915b7..5d7c54c8814 100644 --- a/dgraph/cmd/alpha/run.go +++ b/dgraph/cmd/alpha/run.go @@ -301,6 +301,30 @@ func healthCheck(w http.ResponseWriter, r *http.Request) { _, _ = w.Write(data) } +func stateHandler(w http.ResponseWriter, r *http.Request) { + var err error + x.AddCorsHeaders(w) + w.Header().Set("Content-Type", "application/json") + + ctx := context.Background() + ctx = attachAccessJwt(ctx, r) + + var aResp *api.Response + if aResp, err = (&edgraph.Server{}).State(ctx); err != nil { + x.SetStatus(w, x.Error, err.Error()) + return + } + if aResp == nil { + x.SetStatus(w, x.ErrorNoData, "No state information available.") + return + } + + if _, err = w.Write(aResp.Json); err != nil { + x.SetStatus(w, x.Error, err.Error()) + return + } +} + // storeStatsHandler outputs some basic stats for data store. func storeStatsHandler(w http.ResponseWriter, r *http.Request) { x.AddCorsHeaders(w) @@ -390,6 +414,7 @@ func setupServer() { http.HandleFunc("/commit", commitHandler) http.HandleFunc("/alter", alterHandler) http.HandleFunc("/health", healthCheck) + http.HandleFunc("/state", stateHandler) // TODO: Figure out what this is for? http.HandleFunc("/debug/store", storeStatsHandler) diff --git a/edgraph/access.go b/edgraph/access.go index 859cf50f321..dbbd20deb4c 100644 --- a/edgraph/access.go +++ b/edgraph/access.go @@ -64,3 +64,8 @@ func authorizeQuery(ctx context.Context, parsedReq *gql.Result) error { // always allow access return nil } + +func authorizeState(ctx context.Context) error { + // always allow access + return nil +} diff --git a/edgraph/access_ee.go b/edgraph/access_ee.go index 3e2f16cae43..4bbad779cd4 100644 --- a/edgraph/access_ee.go +++ b/edgraph/access_ee.go @@ -721,3 +721,33 @@ func authorizeQuery(ctx context.Context, parsedReq *gql.Result) error { return err } + +// authorizeState authorizes the State operation +func authorizeState(ctx context.Context) error { + if len(worker.Config.HmacSecret) == 0 { + // the user has not turned on the acl feature + return nil + } + + var userID string + // doAuthorizeState checks if the user is authorized to perform this API request + doAuthorizeState := func() error { + userData, err := extractUserAndGroups(ctx) + switch { + case err == errNoJwt: + return status.Error(codes.PermissionDenied, err.Error()) + case err != nil: + return status.Error(codes.Unauthenticated, err.Error()) + default: + userID = userData[0] + if userID == x.GrootId { + return nil + } + // Deny non groot users. + return status.Error(codes.PermissionDenied, fmt.Sprintf("User is '%v'. "+ + "Only User '%v' is authorized.", userID, x.GrootId)) + } + } + + return doAuthorizeState() +} diff --git a/edgraph/server.go b/edgraph/server.go index ecd48e20d36..f75fd06ad7b 100644 --- a/edgraph/server.go +++ b/edgraph/server.go @@ -17,6 +17,7 @@ package edgraph import ( + "bytes" "encoding/json" "math" "sort" @@ -39,6 +40,7 @@ import ( "github.com/dgraph-io/dgraph/types/facets" "github.com/dgraph-io/dgraph/worker" "github.com/dgraph-io/dgraph/x" + "github.com/gogo/protobuf/jsonpb" "github.com/golang/glog" "github.com/pkg/errors" "golang.org/x/net/context" @@ -580,6 +582,38 @@ type queryContext struct { span *trace.Span } +// State handles state requests +func (s *Server) State(ctx context.Context) (*api.Response, error) { + return s.doState(ctx, NeedAuthorize) +} + +func (s *Server) doState(ctx context.Context, authorize int) ( + *api.Response, error) { + + if ctx.Err() != nil { + return nil, ctx.Err() + } + + if authorize == NeedAuthorize { + if err := authorizeState(ctx); err != nil { + return nil, err + } + } + + ms := worker.GetMembershipState() + if ms == nil { + return nil, errors.Errorf("No membership state found") + } + + m := jsonpb.Marshaler{} + var jsonState bytes.Buffer + if err := m.Marshal(&jsonState, ms); err != nil { + return nil, errors.Errorf("Error marshalling state information to JSON") + } + + return &api.Response{Json: jsonState.Bytes()}, nil +} + // Query handles queries or mutations func (s *Server) Query(ctx context.Context, req *api.Request) (*api.Response, error) { return s.doQuery(ctx, req, NeedAuthorize)