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.
22package iam
23
24import (
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.
37type 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	GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (*pb.Policy, error)
42}
43
44// grpcClient implements client for the standard gRPC-based IAMPolicy service.
45type grpcClient struct {
46	c pb.IAMPolicyClient
47}
48
49var withRetry = gax.WithRetry(func() gax.Retryer {
50	return gax.OnCodes([]codes.Code{
51		codes.DeadlineExceeded,
52		codes.Unavailable,
53	}, gax.Backoff{
54		Initial:    100 * time.Millisecond,
55		Max:        60 * time.Second,
56		Multiplier: 1.3,
57	})
58})
59
60func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) {
61	return g.GetWithVersion(ctx, resource, 1)
62}
63
64func (g *grpcClient) GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (*pb.Policy, error) {
65	var proto *pb.Policy
66	md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
67	ctx = insertMetadata(ctx, md)
68
69	err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
70		var err error
71		proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{
72			Resource: resource,
73			Options: &pb.GetPolicyOptions{
74				RequestedPolicyVersion: requestedPolicyVersion,
75			},
76		})
77		return err
78	}, withRetry)
79	if err != nil {
80		return nil, err
81	}
82	return proto, nil
83}
84
85func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error {
86	md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
87	ctx = insertMetadata(ctx, md)
88
89	return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
90		_, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{
91			Resource: resource,
92			Policy:   p,
93		})
94		return err
95	}, withRetry)
96}
97
98func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
99	var res *pb.TestIamPermissionsResponse
100	md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("%s=%v", "resource", resource))
101	ctx = insertMetadata(ctx, md)
102
103	err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error {
104		var err error
105		res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{
106			Resource:    resource,
107			Permissions: perms,
108		})
109		return err
110	}, withRetry)
111	if err != nil {
112		return nil, err
113	}
114	return res.Permissions, nil
115}
116
117// A Handle provides IAM operations for a resource.
118type Handle struct {
119	c        client
120	resource string
121}
122
123// A Handle3 provides IAM operations for a resource. It is similar to a Handle, but provides access to newer IAM features (e.g., conditions).
124type Handle3 struct {
125	c        client
126	resource string
127	version  int32
128}
129
130// InternalNewHandle is for use by the Google Cloud Libraries only.
131//
132// InternalNewHandle returns a Handle for resource.
133// The conn parameter refers to a server that must support the IAMPolicy service.
134func InternalNewHandle(conn grpc.ClientConnInterface, resource string) *Handle {
135	return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource)
136}
137
138// InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only.
139//
140// InternalNewHandleClient returns a Handle for resource using the given
141// grpc service that implements IAM as a mixin
142func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle {
143	return InternalNewHandleClient(&grpcClient{c: c}, resource)
144}
145
146// InternalNewHandleClient is for use by the Google Cloud Libraries only.
147//
148// InternalNewHandleClient returns a Handle for resource using the given
149// client implementation.
150func InternalNewHandleClient(c client, resource string) *Handle {
151	return &Handle{
152		c:        c,
153		resource: resource,
154	}
155}
156
157// V3 returns a Handle3, which is like Handle except it sets
158// requestedPolicyVersion to 3 when retrieving a policy and policy.version to 3
159// when storing a policy.
160func (h *Handle) V3() *Handle3 {
161	return &Handle3{
162		c:        h.c,
163		resource: h.resource,
164		version:  3,
165	}
166}
167
168// Policy retrieves the IAM policy for the resource.
169func (h *Handle) Policy(ctx context.Context) (*Policy, error) {
170	proto, err := h.c.Get(ctx, h.resource)
171	if err != nil {
172		return nil, err
173	}
174	return &Policy{InternalProto: proto}, nil
175}
176
177// SetPolicy replaces the resource's current policy with the supplied Policy.
178//
179// If policy was created from a prior call to Get, then the modification will
180// only succeed if the policy has not changed since the Get.
181func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error {
182	return h.c.Set(ctx, h.resource, policy.InternalProto)
183}
184
185// TestPermissions returns the subset of permissions that the caller has on the resource.
186func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
187	return h.c.Test(ctx, h.resource, permissions)
188}
189
190// A RoleName is a name representing a collection of permissions.
191type RoleName string
192
193// Common role names.
194const (
195	Owner  RoleName = "roles/owner"
196	Editor RoleName = "roles/editor"
197	Viewer RoleName = "roles/viewer"
198)
199
200const (
201	// AllUsers is a special member that denotes all users, even unauthenticated ones.
202	AllUsers = "allUsers"
203
204	// AllAuthenticatedUsers is a special member that denotes all authenticated users.
205	AllAuthenticatedUsers = "allAuthenticatedUsers"
206)
207
208// A Policy is a list of Bindings representing roles
209// granted to members.
210//
211// The zero Policy is a valid policy with no bindings.
212type Policy struct {
213	// TODO(jba): when type aliases are available, put Policy into an internal package
214	// and provide an exported alias here.
215
216	// This field is exported for use by the Google Cloud Libraries only.
217	// It may become unexported in a future release.
218	InternalProto *pb.Policy
219}
220
221// Members returns the list of members with the supplied role.
222// The return value should not be modified. Use Add and Remove
223// to modify the members of a role.
224func (p *Policy) Members(r RoleName) []string {
225	b := p.binding(r)
226	if b == nil {
227		return nil
228	}
229	return b.Members
230}
231
232// HasRole reports whether member has role r.
233func (p *Policy) HasRole(member string, r RoleName) bool {
234	return memberIndex(member, p.binding(r)) >= 0
235}
236
237// Add adds member member to role r if it is not already present.
238// A new binding is created if there is no binding for the role.
239func (p *Policy) Add(member string, r RoleName) {
240	b := p.binding(r)
241	if b == nil {
242		if p.InternalProto == nil {
243			p.InternalProto = &pb.Policy{}
244		}
245		p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{
246			Role:    string(r),
247			Members: []string{member},
248		})
249		return
250	}
251	if memberIndex(member, b) < 0 {
252		b.Members = append(b.Members, member)
253		return
254	}
255}
256
257// Remove removes member from role r if it is present.
258func (p *Policy) Remove(member string, r RoleName) {
259	bi := p.bindingIndex(r)
260	if bi < 0 {
261		return
262	}
263	bindings := p.InternalProto.Bindings
264	b := bindings[bi]
265	mi := memberIndex(member, b)
266	if mi < 0 {
267		return
268	}
269	// Order doesn't matter for bindings or members, so to remove, move the last item
270	// into the removed spot and shrink the slice.
271	if len(b.Members) == 1 {
272		// Remove binding.
273		last := len(bindings) - 1
274		bindings[bi] = bindings[last]
275		bindings[last] = nil
276		p.InternalProto.Bindings = bindings[:last]
277		return
278	}
279	// Remove member.
280	// TODO(jba): worry about multiple copies of m?
281	last := len(b.Members) - 1
282	b.Members[mi] = b.Members[last]
283	b.Members[last] = ""
284	b.Members = b.Members[:last]
285}
286
287// Roles returns the names of all the roles that appear in the Policy.
288func (p *Policy) Roles() []RoleName {
289	if p.InternalProto == nil {
290		return nil
291	}
292	var rns []RoleName
293	for _, b := range p.InternalProto.Bindings {
294		rns = append(rns, RoleName(b.Role))
295	}
296	return rns
297}
298
299// binding returns the Binding for the suppied role, or nil if there isn't one.
300func (p *Policy) binding(r RoleName) *pb.Binding {
301	i := p.bindingIndex(r)
302	if i < 0 {
303		return nil
304	}
305	return p.InternalProto.Bindings[i]
306}
307
308func (p *Policy) bindingIndex(r RoleName) int {
309	if p.InternalProto == nil {
310		return -1
311	}
312	for i, b := range p.InternalProto.Bindings {
313		if b.Role == string(r) {
314			return i
315		}
316	}
317	return -1
318}
319
320// memberIndex returns the index of m in b's Members, or -1 if not found.
321func memberIndex(m string, b *pb.Binding) int {
322	if b == nil {
323		return -1
324	}
325	for i, mm := range b.Members {
326		if mm == m {
327			return i
328		}
329	}
330	return -1
331}
332
333// insertMetadata inserts metadata into the given context
334func insertMetadata(ctx context.Context, mds ...metadata.MD) context.Context {
335	out, _ := metadata.FromOutgoingContext(ctx)
336	out = out.Copy()
337	for _, md := range mds {
338		for k, v := range md {
339			out[k] = append(out[k], v...)
340		}
341	}
342	return metadata.NewOutgoingContext(ctx, out)
343}
344
345// A Policy3 is a list of Bindings representing roles granted to members.
346//
347// The zero Policy3 is a valid policy with no bindings.
348//
349// It is similar to a Policy, except a Policy3 provides direct access to the
350// list of Bindings.
351//
352// The policy version is always set to 3.
353type Policy3 struct {
354	etag     []byte
355	Bindings []*pb.Binding
356}
357
358// Policy retrieves the IAM policy for the resource.
359//
360// requestedPolicyVersion is always set to 3.
361func (h *Handle3) Policy(ctx context.Context) (*Policy3, error) {
362	proto, err := h.c.GetWithVersion(ctx, h.resource, h.version)
363	if err != nil {
364		return nil, err
365	}
366	return &Policy3{
367		Bindings: proto.Bindings,
368		etag:     proto.Etag,
369	}, nil
370}
371
372// SetPolicy replaces the resource's current policy with the supplied Policy.
373//
374// If policy was created from a prior call to Get, then the modification will
375// only succeed if the policy has not changed since the Get.
376func (h *Handle3) SetPolicy(ctx context.Context, policy *Policy3) error {
377	return h.c.Set(ctx, h.resource, &pb.Policy{
378		Bindings: policy.Bindings,
379		Etag:     policy.etag,
380		Version:  h.version,
381	})
382}
383
384// TestPermissions returns the subset of permissions that the caller has on the resource.
385func (h *Handle3) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
386	return h.c.Test(ctx, h.resource, permissions)
387}
388