go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/auth_service/impl/servers/authdb/server.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package authdb contains methods to work with authdb.
    16  package authdb
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"strconv"
    22  
    23  	"google.golang.org/grpc/codes"
    24  	"google.golang.org/grpc/status"
    25  
    26  	"go.chromium.org/luci/common/errors"
    27  	"go.chromium.org/luci/common/logging"
    28  	"go.chromium.org/luci/gae/service/datastore"
    29  	"go.chromium.org/luci/server/router"
    30  
    31  	"go.chromium.org/luci/auth_service/api/rpcpb"
    32  	"go.chromium.org/luci/auth_service/impl/model"
    33  )
    34  
    35  // Server implements AuthDB server.
    36  type Server struct {
    37  	rpcpb.UnimplementedAuthDBServer
    38  }
    39  
    40  type SnapshotJSON struct {
    41  	AuthDBRev      int64  `json:"auth_db_rev"`
    42  	AuthDBDeflated []byte `json:"deflated_body,omitempty"`
    43  	AuthDBSha256   string `json:"sha256"`
    44  	CreatedTS      int64  `json:"created_ts"`
    45  }
    46  
    47  // GetSnapshot implements the corresponding RPC method.
    48  func (*Server) GetSnapshot(ctx context.Context, request *rpcpb.GetSnapshotRequest) (*rpcpb.Snapshot, error) {
    49  	if request.Revision < 0 {
    50  		return nil, status.Errorf(codes.InvalidArgument, "Negative revision numbers are not valid")
    51  	} else if request.Revision == 0 {
    52  		var err error
    53  		if request.Revision, err = getLatestRevision(ctx, request); err != nil {
    54  			logging.Errorf(ctx, err.Error())
    55  			return nil, err
    56  		}
    57  	}
    58  
    59  	switch snapshot, err := model.GetAuthDBSnapshot(ctx, request.Revision, request.SkipBody, false); {
    60  	case err == nil:
    61  		return snapshot.ToProto(), nil
    62  	case errors.Is(err, datastore.ErrNoSuchEntity):
    63  		return nil, status.Errorf(codes.NotFound, "AuthDB revision %v not found", request.Revision)
    64  	default:
    65  		errStr := "unknown error while calling GetAuthDBSnapshot"
    66  		logging.Errorf(ctx, errStr)
    67  		return nil, status.Errorf(codes.Internal, errStr)
    68  	}
    69  }
    70  
    71  // getLatestRevision is a helper function to set the latest revision number for the GetSnapshotRequest.
    72  func getLatestRevision(ctx context.Context, request *rpcpb.GetSnapshotRequest) (int64, error) {
    73  	switch latest, err := model.GetAuthDBSnapshotLatest(ctx, false); {
    74  	case err == nil:
    75  		return latest.AuthDBRev, nil
    76  	case errors.Is(err, datastore.ErrNoSuchEntity):
    77  		return 0, status.Errorf(codes.NotFound, "AuthDBSnapshotLatest not found")
    78  	default:
    79  		errStr := "unknown error while calling GetAuthDBSnapshotLatest"
    80  		logging.Errorf(ctx, errStr)
    81  		return 0, status.Errorf(codes.Internal, errStr)
    82  	}
    83  }
    84  
    85  // HandleLegacyAuthDBServing handles the AuthDBSnapshot serving for legacy
    86  // services. Writes the AuthDBSnapshot JSON to the router.Writer. gRPC Error is returned
    87  // and adapted to HTTP format if operation is unsuccesful.
    88  func (s *Server) HandleLegacyAuthDBServing(ctx *router.Context) error {
    89  	c, r, w := ctx.Request.Context(), ctx.Request, ctx.Writer
    90  	var snap *rpcpb.Snapshot
    91  	var err error
    92  
    93  	skipBody := r.URL.Query().Get("skip_body") == "1"
    94  
    95  	if revIDStr := ctx.Params.ByName("revID"); revIDStr != "latest" {
    96  		revID, err := strconv.ParseInt(revIDStr, 10, 64)
    97  		switch {
    98  		case err != nil:
    99  			errors.Log(c, errors.Annotate(err, "issue while parsing revID %s", revIDStr).Err())
   100  			return status.Errorf(codes.InvalidArgument, "unable to parse revID: %s", revIDStr)
   101  		case revID < 0:
   102  			return status.Errorf(codes.InvalidArgument, "Negative revision numbers are not valid")
   103  		default:
   104  			snap, err = s.GetSnapshot(c, &rpcpb.GetSnapshotRequest{
   105  				Revision: revID,
   106  				SkipBody: skipBody,
   107  			})
   108  			if err != nil {
   109  				return errors.Annotate(err, "Error while getting snapshot %d", revID).Err()
   110  			}
   111  		}
   112  	} else {
   113  		snap, err = s.GetSnapshot(c, &rpcpb.GetSnapshotRequest{
   114  			Revision: 0,
   115  			SkipBody: skipBody,
   116  		})
   117  		if err != nil {
   118  			return errors.Annotate(err, "Error while getting latest snapshot").Err()
   119  		}
   120  	}
   121  
   122  	unixMicro := snap.CreatedTs.AsTime().UnixNano() / 1000
   123  
   124  	blob, err := json.Marshal(map[string]any{
   125  		"snapshot": SnapshotJSON{
   126  			AuthDBRev:      snap.AuthDbRev,
   127  			AuthDBDeflated: snap.AuthDbDeflated,
   128  			AuthDBSha256:   snap.AuthDbSha256,
   129  			CreatedTS:      unixMicro,
   130  		},
   131  	})
   132  
   133  	if err != nil {
   134  		return errors.Annotate(err, "Error while marshaling JSON").Err()
   135  	}
   136  
   137  	if _, err = w.Write(blob); err != nil {
   138  		return errors.Annotate(err, "Error while writing json").Err()
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  // HandleV2AuthDBServing handles the V2AuthDBSnapshot serving for
   145  // validation. Writes the V2AuthDBSnapshot JSON to the router.Writer.
   146  // gRPC Error is returned and adapted to HTTP format if operation is
   147  // unsuccessful.
   148  //
   149  // TODO: Remove this once we have fully rolled out Auth Service v2
   150  // (b/321019030).
   151  func (s *Server) HandleV2AuthDBServing(ctx *router.Context) error {
   152  	c, r, w := ctx.Request.Context(), ctx.Request, ctx.Writer
   153  
   154  	var revID int64
   155  	if revIDStr := ctx.Params.ByName("revID"); revIDStr != "latest" {
   156  		revID, err := strconv.ParseInt(revIDStr, 10, 64)
   157  		if err != nil {
   158  			errors.Log(c, errors.Annotate(err, "issue while parsing revID %s", revIDStr).Err())
   159  			return status.Errorf(codes.InvalidArgument, "unable to parse revID: %s", revIDStr)
   160  		}
   161  		if revID < 0 {
   162  			return status.Errorf(codes.InvalidArgument, "Negative revision numbers are not valid")
   163  		}
   164  	} else {
   165  		switch latest, err := model.GetAuthDBSnapshotLatest(c, true); {
   166  		case err == nil:
   167  			revID = latest.AuthDBRev
   168  		case errors.Is(err, datastore.ErrNoSuchEntity):
   169  			return status.Errorf(codes.NotFound, "V2AuthDBSnapshotLatest not found")
   170  		default:
   171  			errStr := "unknown error while getting V2AuthDBSnapshotLatest"
   172  			logging.Errorf(c, errStr)
   173  			return status.Errorf(codes.Internal, errStr)
   174  		}
   175  	}
   176  
   177  	skipBody := r.URL.Query().Get("skip_body") == "1"
   178  
   179  	snapshot, err := model.GetAuthDBSnapshot(c, revID, skipBody, true)
   180  	if err != nil {
   181  		if errors.Is(err, datastore.ErrNoSuchEntity) {
   182  			return status.Errorf(codes.NotFound, "V2 AuthDB revision %v not found", revID)
   183  		}
   184  		// Unexpected error.
   185  		errStr := "unknown error while getting V2AuthDBSnapshot"
   186  		logging.Errorf(c, errStr)
   187  		return status.Errorf(codes.Internal, errStr)
   188  	}
   189  
   190  	snap := snapshot.ToProto()
   191  	unixMicro := snap.CreatedTs.AsTime().UnixNano() / 1000
   192  	blob, err := json.Marshal(map[string]any{
   193  		"snapshot": SnapshotJSON{
   194  			AuthDBRev:      snap.AuthDbRev,
   195  			AuthDBDeflated: snap.AuthDbDeflated,
   196  			AuthDBSha256:   snap.AuthDbSha256,
   197  			CreatedTS:      unixMicro,
   198  		},
   199  	})
   200  	if err != nil {
   201  		return errors.Annotate(err, "Error while marshalling JSON").Err()
   202  	}
   203  
   204  	if _, err = w.Write(blob); err != nil {
   205  		return errors.Annotate(err, "Error while writing json").Err()
   206  	}
   207  
   208  	return nil
   209  }