]>
Commit | Line | Data |
---|---|---|
107c1cdb ND |
1 | // Copyright 2016 Google LLC |
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 iam supports the resource-specific operations of Google Cloud | |
16 | // IAM (Identity and Access Management) for the Google Cloud Libraries. | |
17 | // See https://cloud.google.com/iam for more about IAM. | |
18 | // | |
19 | // Users of the Google Cloud Libraries will typically not use this package | |
20 | // directly. Instead they will begin with some resource that supports IAM, like | |
21 | // a pubsub topic, and call its IAM method to get a Handle for that resource. | |
22 | package iam | |
23 | ||
24 | import ( | |
25 | "context" | |
26 | "fmt" | |
27 | "time" | |
28 | ||
29 | gax "github.com/googleapis/gax-go/v2" | |
30 | pb "google.golang.org/genproto/googleapis/iam/v1" | |
31 | "google.golang.org/grpc" | |
32 | "google.golang.org/grpc/codes" | |
33 | "google.golang.org/grpc/metadata" | |
34 | ) | |
35 | ||
36 | // client abstracts the IAMPolicy API to allow multiple implementations. | |
37 | type client interface { | |
38 | Get(ctx context.Context, resource string) (*pb.Policy, error) | |
39 | Set(ctx context.Context, resource string, p *pb.Policy) error | |
40 | Test(ctx context.Context, resource string, perms []string) ([]string, error) | |
41 | } | |
42 | ||
43 | // grpcClient implements client for the standard gRPC-based IAMPolicy service. | |
44 | type grpcClient struct { | |
45 | c pb.IAMPolicyClient | |
46 | } | |
47 | ||
48 | var withRetry = gax.WithRetry(func() gax.Retryer { | |
49 | return gax.OnCodes([]codes.Code{ | |
50 | codes.DeadlineExceeded, | |
51 | codes.Unavailable, | |
52 | }, gax.Backoff{ | |
53 | Initial: 100 * time.Millisecond, | |
54 | Max: 60 * time.Second, | |
55 | Multiplier: 1.3, | |
56 | }) | |
57 | }) | |
58 | ||
59 | func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) { | |
60 | var proto *pb.Policy | |
61 | md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource)) | |
62 | ctx = insertMetadata(ctx, md) | |
63 | ||
64 | err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { | |
65 | var err error | |
66 | proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource}) | |
67 | return err | |
68 | }, withRetry) | |
69 | if err != nil { | |
70 | return nil, err | |
71 | } | |
72 | return proto, nil | |
73 | } | |
74 | ||
75 | func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error { | |
76 | md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource)) | |
77 | ctx = insertMetadata(ctx, md) | |
78 | ||
79 | return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { | |
80 | _, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{ | |
81 | Resource: resource, | |
82 | Policy: p, | |
83 | }) | |
84 | return err | |
85 | }, withRetry) | |
86 | } | |
87 | ||
88 | func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) { | |
89 | var res *pb.TestIamPermissionsResponse | |
90 | md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource)) | |
91 | ctx = insertMetadata(ctx, md) | |
92 | ||
93 | err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { | |
94 | var err error | |
95 | res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{ | |
96 | Resource: resource, | |
97 | Permissions: perms, | |
98 | }) | |
99 | return err | |
100 | }, withRetry) | |
101 | if err != nil { | |
102 | return nil, err | |
103 | } | |
104 | return res.Permissions, nil | |
105 | } | |
106 | ||
107 | // A Handle provides IAM operations for a resource. | |
108 | type Handle struct { | |
109 | c client | |
110 | resource string | |
111 | } | |
112 | ||
113 | // InternalNewHandle is for use by the Google Cloud Libraries only. | |
114 | // | |
115 | // InternalNewHandle returns a Handle for resource. | |
116 | // The conn parameter refers to a server that must support the IAMPolicy service. | |
117 | func InternalNewHandle(conn *grpc.ClientConn, resource string) *Handle { | |
118 | return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource) | |
119 | } | |
120 | ||
121 | // InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only. | |
122 | // | |
123 | // InternalNewHandleClient returns a Handle for resource using the given | |
124 | // grpc service that implements IAM as a mixin | |
125 | func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle { | |
126 | return InternalNewHandleClient(&grpcClient{c: c}, resource) | |
127 | } | |
128 | ||
129 | // InternalNewHandleClient is for use by the Google Cloud Libraries only. | |
130 | // | |
131 | // InternalNewHandleClient returns a Handle for resource using the given | |
132 | // client implementation. | |
133 | func InternalNewHandleClient(c client, resource string) *Handle { | |
134 | return &Handle{ | |
135 | c: c, | |
136 | resource: resource, | |
137 | } | |
138 | } | |
139 | ||
140 | // Policy retrieves the IAM policy for the resource. | |
141 | func (h *Handle) Policy(ctx context.Context) (*Policy, error) { | |
142 | proto, err := h.c.Get(ctx, h.resource) | |
143 | if err != nil { | |
144 | return nil, err | |
145 | } | |
146 | return &Policy{InternalProto: proto}, nil | |
147 | } | |
148 | ||
149 | // SetPolicy replaces the resource's current policy with the supplied Policy. | |
150 | // | |
151 | // If policy was created from a prior call to Get, then the modification will | |
152 | // only succeed if the policy has not changed since the Get. | |
153 | func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error { | |
154 | return h.c.Set(ctx, h.resource, policy.InternalProto) | |
155 | } | |
156 | ||
157 | // TestPermissions returns the subset of permissions that the caller has on the resource. | |
158 | func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) { | |
159 | return h.c.Test(ctx, h.resource, permissions) | |
160 | } | |
161 | ||
162 | // A RoleName is a name representing a collection of permissions. | |
163 | type RoleName string | |
164 | ||
165 | // Common role names. | |
166 | const ( | |
167 | Owner RoleName = "roles/owner" | |
168 | Editor RoleName = "roles/editor" | |
169 | Viewer RoleName = "roles/viewer" | |
170 | ) | |
171 | ||
172 | const ( | |
173 | // AllUsers is a special member that denotes all users, even unauthenticated ones. | |
174 | AllUsers = "allUsers" | |
175 | ||
176 | // AllAuthenticatedUsers is a special member that denotes all authenticated users. | |
177 | AllAuthenticatedUsers = "allAuthenticatedUsers" | |
178 | ) | |
179 | ||
180 | // A Policy is a list of Bindings representing roles | |
181 | // granted to members. | |
182 | // | |
183 | // The zero Policy is a valid policy with no bindings. | |
184 | type Policy struct { | |
185 | // TODO(jba): when type aliases are available, put Policy into an internal package | |
186 | // and provide an exported alias here. | |
187 | ||
188 | // This field is exported for use by the Google Cloud Libraries only. | |
189 | // It may become unexported in a future release. | |
190 | InternalProto *pb.Policy | |
191 | } | |
192 | ||
193 | // Members returns the list of members with the supplied role. | |
194 | // The return value should not be modified. Use Add and Remove | |
195 | // to modify the members of a role. | |
196 | func (p *Policy) Members(r RoleName) []string { | |
197 | b := p.binding(r) | |
198 | if b == nil { | |
199 | return nil | |
200 | } | |
201 | return b.Members | |
202 | } | |
203 | ||
204 | // HasRole reports whether member has role r. | |
205 | func (p *Policy) HasRole(member string, r RoleName) bool { | |
206 | return memberIndex(member, p.binding(r)) >= 0 | |
207 | } | |
208 | ||
209 | // Add adds member member to role r if it is not already present. | |
210 | // A new binding is created if there is no binding for the role. | |
211 | func (p *Policy) Add(member string, r RoleName) { | |
212 | b := p.binding(r) | |
213 | if b == nil { | |
214 | if p.InternalProto == nil { | |
215 | p.InternalProto = &pb.Policy{} | |
216 | } | |
217 | p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{ | |
218 | Role: string(r), | |
219 | Members: []string{member}, | |
220 | }) | |
221 | return | |
222 | } | |
223 | if memberIndex(member, b) < 0 { | |
224 | b.Members = append(b.Members, member) | |
225 | return | |
226 | } | |
227 | } | |
228 | ||
229 | // Remove removes member from role r if it is present. | |
230 | func (p *Policy) Remove(member string, r RoleName) { | |
231 | bi := p.bindingIndex(r) | |
232 | if bi < 0 { | |
233 | return | |
234 | } | |
235 | bindings := p.InternalProto.Bindings | |
236 | b := bindings[bi] | |
237 | mi := memberIndex(member, b) | |
238 | if mi < 0 { | |
239 | return | |
240 | } | |
241 | // Order doesn't matter for bindings or members, so to remove, move the last item | |
242 | // into the removed spot and shrink the slice. | |
243 | if len(b.Members) == 1 { | |
244 | // Remove binding. | |
245 | last := len(bindings) - 1 | |
246 | bindings[bi] = bindings[last] | |
247 | bindings[last] = nil | |
248 | p.InternalProto.Bindings = bindings[:last] | |
249 | return | |
250 | } | |
251 | // Remove member. | |
252 | // TODO(jba): worry about multiple copies of m? | |
253 | last := len(b.Members) - 1 | |
254 | b.Members[mi] = b.Members[last] | |
255 | b.Members[last] = "" | |
256 | b.Members = b.Members[:last] | |
257 | } | |
258 | ||
259 | // Roles returns the names of all the roles that appear in the Policy. | |
260 | func (p *Policy) Roles() []RoleName { | |
261 | if p.InternalProto == nil { | |
262 | return nil | |
263 | } | |
264 | var rns []RoleName | |
265 | for _, b := range p.InternalProto.Bindings { | |
266 | rns = append(rns, RoleName(b.Role)) | |
267 | } | |
268 | return rns | |
269 | } | |
270 | ||
271 | // binding returns the Binding for the suppied role, or nil if there isn't one. | |
272 | func (p *Policy) binding(r RoleName) *pb.Binding { | |
273 | i := p.bindingIndex(r) | |
274 | if i < 0 { | |
275 | return nil | |
276 | } | |
277 | return p.InternalProto.Bindings[i] | |
278 | } | |
279 | ||
280 | func (p *Policy) bindingIndex(r RoleName) int { | |
281 | if p.InternalProto == nil { | |
282 | return -1 | |
283 | } | |
284 | for i, b := range p.InternalProto.Bindings { | |
285 | if b.Role == string(r) { | |
286 | return i | |
287 | } | |
288 | } | |
289 | return -1 | |
290 | } | |
291 | ||
292 | // memberIndex returns the index of m in b's Members, or -1 if not found. | |
293 | func memberIndex(m string, b *pb.Binding) int { | |
294 | if b == nil { | |
295 | return -1 | |
296 | } | |
297 | for i, mm := range b.Members { | |
298 | if mm == m { | |
299 | return i | |
300 | } | |
301 | } | |
302 | return -1 | |
303 | } | |
304 | ||
305 | // insertMetadata inserts metadata into the given context | |
306 | func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context { | |
307 | out, _ := metadata.FromOutgoingContext(ctx) | |
308 | out = out.Copy() | |
309 | for _, md := range mds { | |
310 | for k, v := range md { | |
311 | out[k] = append(out[k], v...) | |
312 | } | |
313 | } | |
314 | return metadata.NewOutgoingContext(ctx, out) | |
315 | } |