go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/appengine/coordinator/context.go (about) 1 // Copyright 2015 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 coordinator 16 17 import ( 18 "context" 19 "fmt" 20 21 "google.golang.org/grpc/codes" 22 "google.golang.org/grpc/status" 23 24 log "go.chromium.org/luci/common/logging" 25 cfglib "go.chromium.org/luci/config" 26 "go.chromium.org/luci/gae/service/info" 27 "go.chromium.org/luci/logdog/api/config/svcconfig" 28 "go.chromium.org/luci/logdog/server/config" 29 ) 30 31 var projectConfigCtxKey = "logdog.coordinator.ProjectConfig" 32 33 // WithProjectNamespace sets the current namespace to the project name. 34 // 35 // Checks the project exists, but doesn't do any ACL checks. 36 // 37 // It will return a user-facing wrapped gRPC error on failure: 38 // - InvalidArgument if the project name is invalid. 39 // - PermissionDenied/Unauthenticated if the project doesn't exist. 40 // - Internal if an internal error occurred. 41 func WithProjectNamespace(c *context.Context, project string) error { 42 ctx := *c 43 44 if err := cfglib.ValidateProjectName(project); err != nil { 45 log.WithError(err).Errorf(ctx, "Project name is invalid.") 46 return status.Errorf(codes.InvalidArgument, "Project name is invalid: %s", err) 47 } 48 49 // Load the project config, thus verifying the project exists. 50 pcfg, err := config.ProjectConfig(ctx, project) 51 switch { 52 case err == cfglib.ErrNoConfig || err == config.ErrInvalidConfig: 53 // If the configuration request was valid, but no configuration could be 54 // loaded, treat this as the user not having READ access to the project. 55 // Otherwise, the user could use this error response to confirm a 56 // project's existence. 57 log.Fields{ 58 log.ErrorKey: err, 59 "project": project, 60 }.Errorf(ctx, "Could not load config for project.") 61 return PermissionDeniedErr(ctx) 62 case err != nil: 63 // The configuration attempt failed to load. This is an internal error, 64 // and is safe to return because it's not contingent on the existence (or 65 // lack thereof) of the project. 66 return status.Error(codes.Internal, "internal server error") 67 } 68 69 // All future datastore queries are scoped to this project. 70 pns := ProjectNamespace(project) 71 nc, err := info.Namespace(ctx, pns) 72 if err != nil { 73 log.Fields{ 74 log.ErrorKey: err, 75 "project": project, 76 "namespace": pns, 77 }.Errorf(ctx, "Failed to set namespace.") 78 return status.Error(codes.Internal, "internal server error") 79 } 80 81 // Store the project config in the context to avoid fetching it again. 82 nc = context.WithValue(nc, &projectConfigCtxKey, pcfg) 83 84 *c = nc 85 return nil 86 } 87 88 // Project returns the current project installed in the supplied Context's 89 // namespace. 90 // 91 // This function is called with the expectation that the Context is in a 92 // namespace conforming to ProjectNamespace. If this is not the case, this 93 // method will panic. 94 func Project(ctx context.Context) string { 95 ns := info.GetNamespace(ctx) 96 project := ProjectFromNamespace(ns) 97 if project != "" { 98 return project 99 } 100 panic(fmt.Errorf("current namespace %q does not begin with project namespace prefix (%q)", ns, ProjectNamespacePrefix)) 101 } 102 103 // ProjectConfig returns the project-specific configuration for the 104 // current project as set in WithProjectNamespace. 105 // 106 // If there is no current project namespace, or if the current project has no 107 // configuration, config.ErrInvalidConfig will be returned. 108 func ProjectConfig(ctx context.Context) (*svcconfig.ProjectConfig, error) { 109 cfg, _ := ctx.Value(&projectConfigCtxKey).(*svcconfig.ProjectConfig) 110 if cfg == nil { 111 return nil, config.ErrInvalidConfig 112 } 113 return cfg, nil 114 }