| // 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 ( |
| "context" |
| "time" |
| |
| gax "github.com/googleapis/gax-go" |
| 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 |
| } |