blob: 5191125d89a49aed11f6a9eb3711c5dd26879746 [file] [log] [blame]
// Copyright 2016 The LUCI Authors.
//
// 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
import (
"bytes"
"encoding/json"
"sort"
)
// Policy is an IAM policy object.
//
// See https://cloud.google.com/iam/reference/rest/v1/Policy.
type Policy struct {
Bindings PolicyBindings
Etag string
// All other JSON fields we are not interested in but must to preserve.
//
// They are assumed to be immutable. Clone and Equals below treat them as
// scalar values, not as pointers to []byte.
UnrecognizedFields map[string]*json.RawMessage
}
// MarshalJSON is part of json.Marshaler interface.
func (p Policy) MarshalJSON() ([]byte, error) {
// We have to use *json.RawMessage instead of json.RawMessage because
// otherwise json reflection magic gets confused and serializes
// json.RawMessage as []byte. See http://play.golang.org/p/iid7Xthp-_.
fields := make(map[string]*json.RawMessage, len(p.UnrecognizedFields)+2)
for k, v := range p.UnrecognizedFields {
fields[k] = v
}
blob, err := json.Marshal(p.Bindings)
if err != nil {
return nil, err
}
bindings := json.RawMessage(blob)
fields["bindings"] = &bindings
blob, err = json.Marshal(p.Etag)
if err != nil {
return nil, err
}
etag := json.RawMessage(blob)
fields["etag"] = &etag
return json.Marshal(fields)
}
// UnmarshalJSON is part of json.Unmarshaler interface.
func (p *Policy) UnmarshalJSON(data []byte) error {
var fields map[string]*json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return err
}
newOne := Policy{
Bindings: make(PolicyBindings),
UnrecognizedFields: fields,
}
if raw, ok := fields["bindings"]; ok && raw != nil {
delete(fields, "bindings")
if err := json.Unmarshal([]byte(*raw), &newOne.Bindings); err != nil {
return err
}
}
if raw, ok := fields["etag"]; ok && raw != nil {
delete(fields, "etag")
if err := json.Unmarshal([]byte(*raw), &newOne.Etag); err != nil {
return err
}
}
*p = newOne
return nil
}
// Clone makes a deep copy of this object.
func (p Policy) Clone() Policy {
c := p
if p.Bindings != nil {
c.Bindings = p.Bindings.Clone()
}
if p.UnrecognizedFields != nil {
c.UnrecognizedFields = make(map[string]*json.RawMessage, len(p.UnrecognizedFields))
for k, v := range p.UnrecognizedFields {
c.UnrecognizedFields[k] = v
}
}
return c
}
// Equals returns true if this object is equal to another one.
func (p Policy) Equals(another Policy) bool {
if p.Etag != another.Etag {
return false
}
if !p.Bindings.Equals(another.Bindings) {
return false
}
if len(p.UnrecognizedFields) != len(another.UnrecognizedFields) {
return false
}
for k, left := range p.UnrecognizedFields {
right, hasIt := another.UnrecognizedFields[k]
if !hasIt || !isEqualRawMsg(left, right) {
return false
}
}
return true
}
func isEqualRawMsg(left, right *json.RawMessage) bool {
switch {
case left == right:
return true
case left == nil && right != nil:
return false
case left != nil && right == nil:
return false
default:
return bytes.Equal([]byte(*left), []byte(*right))
}
}
// GrantRole grants a role to the given set of principals.
func (p *Policy) GrantRole(role string, principals ...string) {
if len(principals) == 0 {
return
}
if p.Bindings == nil {
p.Bindings = make(PolicyBindings, 1)
}
members := p.Bindings[role]
if members == nil {
members = make(membersSet, len(principals))
p.Bindings[role] = members
}
for _, principal := range principals {
members[principal] = struct{}{}
}
}
// RevokeRole removes a role from the given set of principals.
func (p *Policy) RevokeRole(role string, principals ...string) {
members := p.Bindings[role]
for _, principal := range principals {
delete(members, principal)
}
}
// membersSet is a set of members that have been granted some role.
type membersSet map[string]struct{}
// PolicyBindings is the IAM policy map {role -> set of members}.
//
// Implements json.Marshaler and json.Unmarshaler.
type PolicyBindings map[string]membersSet
type roleBinding struct {
Role string `json:"role"`
Members []string `json:"members,omitempty"`
}
// MarshalJSON is part of json.Marshaler interface.
func (b PolicyBindings) MarshalJSON() ([]byte, error) {
roles := make([]string, 0, len(b))
for role, members := range b {
if len(members) != 0 {
roles = append(roles, role)
}
}
sort.Strings(roles)
bindings := make([]roleBinding, 0, len(roles))
for _, role := range roles {
members := make([]string, 0, len(b[role]))
for k := range b[role] {
members = append(members, k)
}
sort.Strings(members)
bindings = append(bindings, roleBinding{
Role: role,
Members: members,
})
}
return json.Marshal(bindings)
}
// UnmarshalJSON is part of json.Unmarshaler interface.
func (b *PolicyBindings) UnmarshalJSON(data []byte) error {
bindings := []roleBinding{}
if err := json.Unmarshal(data, &bindings); err != nil {
return err
}
newOne := make(PolicyBindings, len(bindings))
for _, item := range bindings {
members := make(membersSet, len(item.Members))
for _, member := range item.Members {
members[member] = struct{}{}
}
newOne[item.Role] = members
}
*b = newOne
return nil
}
// Clone makes a deep copy of this object.
func (b PolicyBindings) Clone() PolicyBindings {
clone := make(PolicyBindings, len(b))
for role, members := range b {
membersCopy := make(membersSet, len(members))
for k, v := range members {
membersCopy[k] = v
}
clone[role] = membersCopy
}
return clone
}
// Equals returns true if this object is equal to another one.
func (b PolicyBindings) Equals(another PolicyBindings) bool {
if len(b) != len(another) {
return false
}
for role, right := range another {
left := b[role]
if len(left) != len(right) {
return false
}
for item := range left {
if _, hasIt := right[item]; !hasIt {
return false
}
}
}
return true
}