Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(889)

Side by Side Diff: appengine/logdog/coordinator/config/projects.go

Issue 1971493003: LogDog: Project READ access for user endpoints. (Closed) Base URL: https://github.com/luci/luci-go@logdog-project-service-config
Patch Set: Added project archival parametrs, better support. Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 package config 5 package config
6 6
7 import ( 7 import (
8 "fmt" 8 "fmt"
9 "strings"
10 9
11 "github.com/golang/protobuf/proto" 10 "github.com/golang/protobuf/proto"
12 "github.com/luci/luci-go/common/config" 11 "github.com/luci/luci-go/common/config"
13 "github.com/luci/luci-go/common/errors"
14 log "github.com/luci/luci-go/common/logging" 12 log "github.com/luci/luci-go/common/logging"
15 "github.com/luci/luci-go/common/parallel"
16 configProto "github.com/luci/luci-go/common/proto/config"
17 "github.com/luci/luci-go/common/proto/logdog/svcconfig" 13 "github.com/luci/luci-go/common/proto/logdog/svcconfig"
18 "github.com/luci/luci-go/server/auth"
19 "github.com/luci/luci-go/server/auth/identity"
20 "golang.org/x/net/context" 14 "golang.org/x/net/context"
21 ) 15 )
22 16
23 const maxProjectWorkers = 32 17 const maxProjectWorkers = 32
24 18
25 // ErrNoAccess is returned if the user has no access to the requested project.
26 var ErrNoAccess = errors.New("no access")
27
28 // ProjectConfigPath returns the config set and path for project-specific 19 // ProjectConfigPath returns the config set and path for project-specific
29 // configuration. 20 // configuration.
30 func ProjectConfigPath(project config.ProjectName) (string, string) { 21 func ProjectConfigPath(project config.ProjectName) (string, string) {
31 return fmt.Sprintf("projects/%s", project), svcconfig.ProjectConfigFilen ame 22 return fmt.Sprintf("projects/%s", project), svcconfig.ProjectConfigFilen ame
32 } 23 }
33 24
34 // ProjectConfig loads the the project config protobuf from the config service. 25 // ProjectConfig loads the the project config protobuf from the config service.
35 // 26 //
36 // If the configuration was not present, config.ErrNoConfig will be returned. 27 // If the configuration was not present, config.ErrNoConfig will be returned.
37 func ProjectConfig(c context.Context, project config.ProjectName) (*svcconfig.Pr ojectConfig, error) { 28 func ProjectConfig(c context.Context, project config.ProjectName) (*svcconfig.Pr ojectConfig, error) {
(...skipping 18 matching lines...) Expand all
56 "contentHash": cfg.ContentHash, 47 "contentHash": cfg.ContentHash,
57 "path": cfg.Path, 48 "path": cfg.Path,
58 "configSet": cfg.ConfigSet, 49 "configSet": cfg.ConfigSet,
59 }.Errorf(c, "Failed to unmarshal project configuration.") 50 }.Errorf(c, "Failed to unmarshal project configuration.")
60 return nil, ErrInvalidConfig 51 return nil, ErrInvalidConfig
61 } 52 }
62 53
63 return &pcfg, nil 54 return &pcfg, nil
64 } 55 }
65 56
66 // Projects lists the registered LogDog projects. 57 // Projects lists the set of projects registered with the config service.
67 func Projects(c context.Context) ([]string, error) { 58 func Projects(c context.Context) ([]config.ProjectName, error) {
68 projects, err := config.Get(c).GetProjects() 59 projects, err := config.Get(c).GetProjects()
69 if err != nil { 60 if err != nil {
70 log.WithError(err).Errorf(c, "Failed to list 'luci-config' proje cts.") 61 log.WithError(err).Errorf(c, "Failed to list 'luci-config' proje cts.")
71 return nil, err 62 return nil, err
72 } 63 }
73 64
74 » // TODO(dnj): Filter this list to projects with active LogDog configs, o nce we 65 » ids := make([]config.ProjectName, len(projects))
75 » // move to project-specific configurations.
76
77 » ids := make([]string, len(projects))
78 for i, p := range projects { 66 for i, p := range projects {
79 » » ids[i] = p.ID 67 » » ids[i] = config.ProjectName(p.ID)
80 } 68 }
81 69
82 // TODO(dnj): Restrict this by actual namespaces in datastore.
83 return ids, nil 70 return ids, nil
84 } 71 }
85
86 // AssertProjectAccess attempts to assert the current user's ability to access
87 // a given project.
88 //
89 // If the user cannot access the referenced project, ErrNoAccess will be
90 // returned. If an error occurs during checking, that error will be returned.
91 // The only time nil will be returned is if the check succeeded and the user was
92 // verified to have access to the requested project.
93 //
94 // NOTE: In the future, this will involve loading LogDog ACLs from the project's
95 // configuration. For now, though, since ACLs aren't implemented (yet), we will
96 // be content with asserting that the user has access to the base project.
97 func AssertProjectAccess(c context.Context, project config.ProjectName) error {
98 // Get our authenticator.
99 st := auth.GetState(c)
100 if st == nil {
101 log.Errorf(c, "No authentication state in Context.")
102 return errors.New("no authentication state in context")
103 }
104
105 hasAccess, err := checkProjectAccess(c, config.Get(c), project, st)
106 if err != nil {
107 log.Fields{
108 log.ErrorKey: err,
109 "project": project,
110 }.Errorf(c, "Failed to check for project access.")
111 return err
112 }
113
114 if !hasAccess {
115 log.Fields{
116 log.ErrorKey: err,
117 "project": project,
118 "identity": st.User().Identity,
119 }.Errorf(c, "User does not have access to this project.")
120 return ErrNoAccess
121 }
122
123 return nil
124 }
125
126 // UserProjects returns the list of luci-config projects that the current user
127 // has access to.
128 //
129 // It does this by listing the full set of luci-config projects, then loading
130 // the project configuration for each one. If the project configuration
131 // specifies an access restriction that the user satisfies, that project will
132 // be included in the list.
133 //
134 // In a production environment, most of the config accesses will be cached.
135 //
136 // If the current user is anonymous, this will still work, returning the set of
137 // projects that the anonymous user can access.
138 func UserProjects(c context.Context) ([]config.ProjectName, error) {
139 // NOTE: This client has a relatively short timeout, and using WithDeadl ine
140 // will apply to all serial calls. We may want to make getting a config client
141 // instance a coordinator.Service function if this proves to be a proble m.
142 ci := config.Get(c)
143
144 // Get our authenticator.
145 st := auth.GetState(c)
146 if st == nil {
147 log.Errorf(c, "No authentication state in Context.")
148 return nil, errors.New("no authentication state in context")
149 }
150
151 allProjects, err := ci.GetProjects()
152 if err != nil {
153 log.WithError(err).Errorf(c, "Failed to list all projects.")
154 return nil, err
155 }
156
157 // In parallel, pull each project's configuration and assert access.
158 access := make([]bool, len(allProjects))
159 err = parallel.WorkPool(maxProjectWorkers, func(taskC chan<- func() erro r) {
160 for i, proj := range allProjects {
161 i := i
162 proj := config.ProjectName(proj.ID)
163
164 taskC <- func() error {
165 hasAccess, err := checkProjectAccess(c, ci, proj , st)
166 switch err {
167 case nil:
168 access[i] = hasAccess
169 return nil
170
171 case config.ErrNoConfig:
172 // No configuration for this project, so nothing to check. Assume no
173 // access.
174 return nil
175
176 default:
177 log.Fields{
178 log.ErrorKey: err,
179 "project": proj,
180 }.Errorf(c, "Failed to check project acc ess.")
181 return err
182 }
183 }
184 }
185 })
186 if err != nil {
187 log.WithError(err).Errorf(c, "Error during project access check. ")
188 return nil, errors.SingleError(err)
189 }
190
191 result := make([]config.ProjectName, 0, len(allProjects))
192 for i, proj := range allProjects {
193 if access[i] {
194 result = append(result, config.ProjectName(proj.ID))
195 }
196 }
197 return result, nil
198 }
199
200 func checkProjectAccess(c context.Context, ci config.Interface, proj config.Proj ectName, st auth.State) (bool, error) {
201 // Load the configuration for this project.
202 cfg, err := ci.GetConfig(fmt.Sprintf("projects/%s", proj), "project.cfg" , false)
203 if err != nil {
204 if err == config.ErrNoConfig {
205 // If the configuration is missing, report no access.
206 return false, nil
207 }
208 return false, err
209 }
210
211 var pcfg configProto.ProjectCfg
212 if err := proto.UnmarshalText(cfg.Content, &pcfg); err != nil {
213 log.Fields{
214 log.ErrorKey: err,
215 "project": proj,
216 }.Errorf(c, "Failed to unmarshal project configuration.")
217 return false, err
218 }
219
220 // Vet project access using the current Authenticator state.
221 id := st.User().Identity
222
223 for _, v := range pcfg.Access {
224 // Is this a group access?
225 access, isGroup := trimPrefix(v, "group:")
226 if isGroup {
227 // Check group membership.
228 isMember, err := st.DB().IsMember(c, id, access)
229 if err != nil {
230 return false, fmt.Errorf("failed to check access %q: %v", v, err)
231 }
232 if isMember {
233 log.Fields{
234 "project": proj,
235 "identity": id,
236 "group": access,
237 }.Debugf(c, "Identity has group membership.")
238 return true, nil
239 }
240 return false, nil
241 }
242
243 // "access" is either an e-mail or an identity. If it is an e-ma il,
244 // transform it into an identity.
245 if idx := strings.IndexRune(access, ':'); idx < 0 {
246 // Presumably an e-mail, convert e-mail to user identity .
247 access = "user:" + access
248 }
249
250 if id == identity.Identity(access) {
251 // Check identity.
252 log.Fields{
253 "project": proj,
254 "identity": id,
255 "accessIdentity": v,
256 }.Debugf(c, "Identity is included.")
257 return true, nil
258 }
259 }
260
261 return false, nil
262 }
263
264 func trimPrefix(s, p string) (string, bool) {
265 if strings.HasPrefix(s, p) {
266 return s[len(p):], true
267 }
268 return s, false
269 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698