...

Source file src/cloud.google.com/go/iam/iam.go

Documentation: cloud.google.com/go/iam

     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  	pb "cloud.google.com/go/iam/apiv1/iampb"
    30  	gax "github.com/googleapis/gax-go/v2"
    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  	GetWithVersion(ctx context.Context, resource string, requestedPolicyVersion int32) (*pb.Policy, error)
    42  }
    43  
    44  // grpcClient implements client for the standard gRPC-based IAMPolicy service.
    45  type grpcClient struct {
    46  	c pb.IAMPolicyClient
    47  }
    48  
    49  var 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  
    60  func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) {
    61  	return g.GetWithVersion(ctx, resource, 1)
    62  }
    63  
    64  func (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  
    85  func (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  
    98  func (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.
   118  type 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).
   124  type 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.
   134  func 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
   142  func 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.
   150  func 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.
   160  func (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.
   169  func (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.
   181  func (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.
   186  func (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.
   191  type RoleName string
   192  
   193  // Common role names.
   194  const (
   195  	Owner  RoleName = "roles/owner"
   196  	Editor RoleName = "roles/editor"
   197  	Viewer RoleName = "roles/viewer"
   198  )
   199  
   200  const (
   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.
   212  type 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.
   224  func (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.
   233  func (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.
   239  func (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.
   258  func (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.
   288  func (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.
   300  func (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  
   308  func (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.
   321  func 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
   334  func 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.
   353  type 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.
   361  func (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.
   376  func (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.
   385  func (h *Handle3) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
   386  	return h.c.Test(ctx, h.resource, permissions)
   387  }
   388  

View as plain text