// Copyright 2016 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package iam supports the resource-specific operations of Google Cloud // IAM (Identity and Access Management) for the Google Cloud Libraries. // See https://cloud.google.com/iam for more about IAM. // // Users of the Google Cloud Libraries will typically not use this package // directly. Instead they will begin with some resource that supports IAM, like // a pubsub topic, and call its IAM method to get a Handle for that resource. package iam import ( "time" gax "github.com/googleapis/gax-go" "golang.org/x/net/context" pb "google.golang.org/genproto/googleapis/iam/v1" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) // client abstracts the IAMPolicy API to allow multiple implementations. type client interface { Get(ctx context.Context, resource string) (*pb.Policy, error) Set(ctx context.Context, resource string, p *pb.Policy) error Test(ctx context.Context, resource string, perms []string) ([]string, error) } // grpcClient implements client for the standard gRPC-based IAMPolicy service. type grpcClient struct { c pb.IAMPolicyClient } var withRetry = gax.WithRetry(func() gax.Retryer { return gax.OnCodes([]codes.Code{ codes.DeadlineExceeded, codes.Unavailable, }, gax.Backoff{ Initial: 100 * time.Millisecond, Max: 60 * time.Second, Multiplier: 1.3, }) }) func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) { var proto *pb.Policy err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { var err error proto, err = g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource}) return err }, withRetry) if err != nil { return nil, err } return proto, nil } func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error { return gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { _, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{ Resource: resource, Policy: p, }) return err }, withRetry) } func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) { var res *pb.TestIamPermissionsResponse err := gax.Invoke(ctx, func(ctx context.Context, _ gax.CallSettings) error { var err error res, err = g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{ Resource: resource, Permissions: perms, }) return err }, withRetry) if err != nil { return nil, err } return res.Permissions, nil } // A Handle provides IAM operations for a resource. type Handle struct { c client resource string } // InternalNewHandle is for use by the Google Cloud Libraries only. // // InternalNewHandle returns a Handle for resource. // The conn parameter refers to a server that must support the IAMPolicy service. func InternalNewHandle(conn *grpc.ClientConn, resource string) *Handle { return InternalNewHandleGRPCClient(pb.NewIAMPolicyClient(conn), resource) } // InternalNewHandleGRPCClient is for use by the Google Cloud Libraries only. // // InternalNewHandleClient returns a Handle for resource using the given // grpc service that implements IAM as a mixin func InternalNewHandleGRPCClient(c pb.IAMPolicyClient, resource string) *Handle { return InternalNewHandleClient(&grpcClient{c: c}, resource) } // InternalNewHandleClient is for use by the Google Cloud Libraries only. // // InternalNewHandleClient returns a Handle for resource using the given // client implementation. func InternalNewHandleClient(c client, resource string) *Handle { return &Handle{ c: c, resource: resource, } } // Policy retrieves the IAM policy for the resource. func (h *Handle) Policy(ctx context.Context) (*Policy, error) { proto, err := h.c.Get(ctx, h.resource) if err != nil { return nil, err } return &Policy{InternalProto: proto}, nil } // SetPolicy replaces the resource's current policy with the supplied Policy. // // If policy was created from a prior call to Get, then the modification will // only succeed if the policy has not changed since the Get. func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error { return h.c.Set(ctx, h.resource, policy.InternalProto) } // TestPermissions returns the subset of permissions that the caller has on the resource. func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) { return h.c.Test(ctx, h.resource, permissions) } // A RoleName is a name representing a collection of permissions. type RoleName string // Common role names. const ( Owner RoleName = "roles/owner" Editor RoleName = "roles/editor" Viewer RoleName = "roles/viewer" ) const ( // AllUsers is a special member that denotes all users, even unauthenticated ones. AllUsers = "allUsers" // AllAuthenticatedUsers is a special member that denotes all authenticated users. AllAuthenticatedUsers = "allAuthenticatedUsers" ) // A Policy is a list of Bindings representing roles // granted to members. // // The zero Policy is a valid policy with no bindings. type Policy struct { // TODO(jba): when type aliases are available, put Policy into an internal package // and provide an exported alias here. // This field is exported for use by the Google Cloud Libraries only. // It may become unexported in a future release. InternalProto *pb.Policy } // Members returns the list of members with the supplied role. // The return value should not be modified. Use Add and Remove // to modify the members of a role. func (p *Policy) Members(r RoleName) []string { b := p.binding(r) if b == nil { return nil } return b.Members } // HasRole reports whether member has role r. func (p *Policy) HasRole(member string, r RoleName) bool { return memberIndex(member, p.binding(r)) >= 0 } // Add adds member member to role r if it is not already present. // A new binding is created if there is no binding for the role. func (p *Policy) Add(member string, r RoleName) { b := p.binding(r) if b == nil { if p.InternalProto == nil { p.InternalProto = &pb.Policy{} } p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{ Role: string(r), Members: []string{member}, }) return } if memberIndex(member, b) < 0 { b.Members = append(b.Members, member) return } } // Remove removes member from role r if it is present. func (p *Policy) Remove(member string, r RoleName) { bi := p.bindingIndex(r) if bi < 0 { return } bindings := p.InternalProto.Bindings b := bindings[bi] mi := memberIndex(member, b) if mi < 0 { return } // Order doesn't matter for bindings or members, so to remove, move the last item // into the removed spot and shrink the slice. if len(b.Members) == 1 { // Remove binding. last := len(bindings) - 1 bindings[bi] = bindings[last] bindings[last] = nil p.InternalProto.Bindings = bindings[:last] return } // Remove member. // TODO(jba): worry about multiple copies of m? last := len(b.Members) - 1 b.Members[mi] = b.Members[last] b.Members[last] = "" b.Members = b.Members[:last] } // Roles returns the names of all the roles that appear in the Policy. func (p *Policy) Roles() []RoleName { if p.InternalProto == nil { return nil } var rns []RoleName for _, b := range p.InternalProto.Bindings { rns = append(rns, RoleName(b.Role)) } return rns } // binding returns the Binding for the suppied role, or nil if there isn't one. func (p *Policy) binding(r RoleName) *pb.Binding { i := p.bindingIndex(r) if i < 0 { return nil } return p.InternalProto.Bindings[i] } func (p *Policy) bindingIndex(r RoleName) int { if p.InternalProto == nil { return -1 } for i, b := range p.InternalProto.Bindings { if b.Role == string(r) { return i } } return -1 } // memberIndex returns the index of m in b's Members, or -1 if not found. func memberIndex(m string, b *pb.Binding) int { if b == nil { return -1 } for i, mm := range b.Members { if mm == m { return i } } return -1 }