You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
8.0 KiB
284 lines
8.0 KiB
// Copyright 2016 Google Inc. All Rights Reserved. |
|
// |
|
// 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 InternalNewHandleClient(&grpcClient{c: pb.NewIAMPolicyClient(conn)}, 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 |
|
}
|
|
|