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.
 
 
 

293 lines
8.3 KiB

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