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 }