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

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

Powered by Google App Engine
This is Rietveld 408576698