1// Copyright 2016 Google Inc. All Rights Reserved.
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	"golang.org/x/net/context"
26	pb "google.golang.org/genproto/googleapis/iam/v1"
27	"google.golang.org/grpc"
28)
29
30// client abstracts the IAMPolicy API to allow multiple implementations.
31type client interface {
32	Get(ctx context.Context, resource string) (*pb.Policy, error)
33	Set(ctx context.Context, resource string, p *pb.Policy) error
34	Test(ctx context.Context, resource string, perms []string) ([]string, error)
35}
36
37// grpcClient implements client for the standard gRPC-based IAMPolicy service.
38type grpcClient struct {
39	c pb.IAMPolicyClient
40}
41
42func (g *grpcClient) Get(ctx context.Context, resource string) (*pb.Policy, error) {
43	proto, err := g.c.GetIamPolicy(ctx, &pb.GetIamPolicyRequest{Resource: resource})
44	if err != nil {
45		return nil, err
46	}
47	return proto, nil
48}
49func (g *grpcClient) Set(ctx context.Context, resource string, p *pb.Policy) error {
50	_, err := g.c.SetIamPolicy(ctx, &pb.SetIamPolicyRequest{
51		Resource: resource,
52		Policy:   p,
53	})
54	return err
55}
56
57func (g *grpcClient) Test(ctx context.Context, resource string, perms []string) ([]string, error) {
58	res, err := g.c.TestIamPermissions(ctx, &pb.TestIamPermissionsRequest{
59		Resource:    resource,
60		Permissions: perms,
61	})
62	if err != nil {
63		return nil, err
64	}
65	return res.Permissions, nil
66}
67
68// A Handle provides IAM operations for a resource.
69type Handle struct {
70	c        client
71	resource string
72}
73
74// InternalNewHandle is for use by the Google Cloud Libraries only.
75//
76// InternalNewHandle returns a Handle for resource.
77// The conn parameter refers to a server that must support the IAMPolicy service.
78func InternalNewHandle(conn *grpc.ClientConn, resource string) *Handle {
79	return InternalNewHandleClient(&grpcClient{c: pb.NewIAMPolicyClient(conn)}, resource)
80}
81
82// InternalNewHandleClient is for use by the Google Cloud Libraries only.
83//
84// InternalNewHandleClient returns a Handle for resource using the given
85// client implementation.
86func InternalNewHandleClient(c client, resource string) *Handle {
87	return &Handle{
88		c:        c,
89		resource: resource,
90	}
91}
92
93// Policy retrieves the IAM policy for the resource.
94func (h *Handle) Policy(ctx context.Context) (*Policy, error) {
95	proto, err := h.c.Get(ctx, h.resource)
96	if err != nil {
97		return nil, err
98	}
99	return &Policy{InternalProto: proto}, nil
100}
101
102// SetPolicy replaces the resource's current policy with the supplied Policy.
103//
104// If policy was created from a prior call to Get, then the modification will
105// only succeed if the policy has not changed since the Get.
106func (h *Handle) SetPolicy(ctx context.Context, policy *Policy) error {
107	return h.c.Set(ctx, h.resource, policy.InternalProto)
108}
109
110// TestPermissions returns the subset of permissions that the caller has on the resource.
111func (h *Handle) TestPermissions(ctx context.Context, permissions []string) ([]string, error) {
112	return h.c.Test(ctx, h.resource, permissions)
113}
114
115// A RoleName is a name representing a collection of permissions.
116type RoleName string
117
118// Common role names.
119const (
120	Owner  RoleName = "roles/owner"
121	Editor RoleName = "roles/editor"
122	Viewer RoleName = "roles/viewer"
123)
124
125const (
126	// AllUsers is a special member that denotes all users, even unauthenticated ones.
127	AllUsers = "allUsers"
128
129	// AllAuthenticatedUsers is a special member that denotes all authenticated users.
130	AllAuthenticatedUsers = "allAuthenticatedUsers"
131)
132
133// A Policy is a list of Bindings representing roles
134// granted to members.
135//
136// The zero Policy is a valid policy with no bindings.
137type Policy struct {
138	// TODO(jba): when type aliases are available, put Policy into an internal package
139	// and provide an exported alias here.
140
141	// This field is exported for use by the Google Cloud Libraries only.
142	// It may become unexported in a future release.
143	InternalProto *pb.Policy
144}
145
146// Members returns the list of members with the supplied role.
147// The return value should not be modified. Use Add and Remove
148// to modify the members of a role.
149func (p *Policy) Members(r RoleName) []string {
150	b := p.binding(r)
151	if b == nil {
152		return nil
153	}
154	return b.Members
155}
156
157// HasRole reports whether member has role r.
158func (p *Policy) HasRole(member string, r RoleName) bool {
159	return memberIndex(member, p.binding(r)) >= 0
160}
161
162// Add adds member member to role r if it is not already present.
163// A new binding is created if there is no binding for the role.
164func (p *Policy) Add(member string, r RoleName) {
165	b := p.binding(r)
166	if b == nil {
167		if p.InternalProto == nil {
168			p.InternalProto = &pb.Policy{}
169		}
170		p.InternalProto.Bindings = append(p.InternalProto.Bindings, &pb.Binding{
171			Role:    string(r),
172			Members: []string{member},
173		})
174		return
175	}
176	if memberIndex(member, b) < 0 {
177		b.Members = append(b.Members, member)
178		return
179	}
180}
181
182// Remove removes member from role r if it is present.
183func (p *Policy) Remove(member string, r RoleName) {
184	bi := p.bindingIndex(r)
185	if bi < 0 {
186		return
187	}
188	bindings := p.InternalProto.Bindings
189	b := bindings[bi]
190	mi := memberIndex(member, b)
191	if mi < 0 {
192		return
193	}
194	// Order doesn't matter for bindings or members, so to remove, move the last item
195	// into the removed spot and shrink the slice.
196	if len(b.Members) == 1 {
197		// Remove binding.
198		last := len(bindings) - 1
199		bindings[bi] = bindings[last]
200		bindings[last] = nil
201		p.InternalProto.Bindings = bindings[:last]
202		return
203	}
204	// Remove member.
205	// TODO(jba): worry about multiple copies of m?
206	last := len(b.Members) - 1
207	b.Members[mi] = b.Members[last]
208	b.Members[last] = ""
209	b.Members = b.Members[:last]
210}
211
212// Roles returns the names of all the roles that appear in the Policy.
213func (p *Policy) Roles() []RoleName {
214	if p.InternalProto == nil {
215		return nil
216	}
217	var rns []RoleName
218	for _, b := range p.InternalProto.Bindings {
219		rns = append(rns, RoleName(b.Role))
220	}
221	return rns
222}
223
224// binding returns the Binding for the suppied role, or nil if there isn't one.
225func (p *Policy) binding(r RoleName) *pb.Binding {
226	i := p.bindingIndex(r)
227	if i < 0 {
228		return nil
229	}
230	return p.InternalProto.Bindings[i]
231}
232
233func (p *Policy) bindingIndex(r RoleName) int {
234	if p.InternalProto == nil {
235		return -1
236	}
237	for i, b := range p.InternalProto.Bindings {
238		if b.Role == string(r) {
239			return i
240		}
241	}
242	return -1
243}
244
245// memberIndex returns the index of m in b's Members, or -1 if not found.
246func memberIndex(m string, b *pb.Binding) int {
247	if b == nil {
248		return -1
249	}
250	for i, mm := range b.Members {
251		if mm == m {
252			return i
253		}
254	}
255	return -1
256}
257