package eacl import ( "crypto/ecdsa" v2acl "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" ) // Record of the ContainerEACL rule, that defines ContainerEACL action, targets for this action, // object service operation and filters for request headers. // // Record is compatible with v2 acl.EACLRecord message. type Record struct { action Action operation Operation filters []Filter targets []Target } // Targets returns list of target subjects to apply ACL rule to. func (r Record) Targets() []Target { return r.targets } // SetTargets sets list of target subjects to apply ACL rule to. func (r *Record) SetTargets(targets ...Target) { r.targets = targets } // Filters returns list of filters to match and see if rule is applicable. func (r Record) Filters() []Filter { return r.filters } // Operation returns FrostFS request verb to match. func (r Record) Operation() Operation { return r.operation } // SetOperation sets FrostFS request verb to match. func (r *Record) SetOperation(operation Operation) { r.operation = operation } // Action returns rule execution result. func (r Record) Action() Action { return r.action } // SetAction sets rule execution result. func (r *Record) SetAction(action Action) { r.action = action } // AddRecordTarget adds single Target to the Record. func AddRecordTarget(r *Record, t *Target) { r.SetTargets(append(r.Targets(), *t)...) } // AddFormedTarget forms Target with specified Role and list of // ECDSA public keys and adds it to the Record. func AddFormedTarget(r *Record, role Role, keys ...ecdsa.PublicKey) { t := NewTarget() t.SetRole(role) SetTargetECDSAKeys(t, ecdsaKeysToPtrs(keys)...) AddRecordTarget(r, t) } type stringEncoder interface { EncodeToString() string } func (r *Record) addFilter(from FilterHeaderType, m Match, keyTyp filterKeyType, key string, val stringEncoder) { filter := Filter{ from: from, key: filterKey{ typ: keyTyp, str: key, }, matcher: m, value: val, } r.filters = append(r.filters, filter) } func (r *Record) addObjectFilter(m Match, keyTyp filterKeyType, key string, val stringEncoder) { r.addFilter(HeaderFromObject, m, keyTyp, key, val) } func (r *Record) addObjectReservedFilter(m Match, typ filterKeyType, val stringEncoder) { r.addObjectFilter(m, typ, "", val) } // AddFilter adds generic filter. func (r *Record) AddFilter(from FilterHeaderType, matcher Match, name, value string) { r.addFilter(from, matcher, 0, name, staticStringer(value)) } // AddObjectAttributeFilter adds filter by object attribute. func (r *Record) AddObjectAttributeFilter(m Match, key, value string) { r.addObjectFilter(m, 0, key, staticStringer(value)) } // AddObjectVersionFilter adds filter by object version. func (r *Record) AddObjectVersionFilter(m Match, v *version.Version) { r.addObjectReservedFilter(m, fKeyObjVersion, staticStringer(version.EncodeToString(*v))) } // AddObjectIDFilter adds filter by object ID. func (r *Record) AddObjectIDFilter(m Match, id oid.ID) { r.addObjectReservedFilter(m, fKeyObjID, id) } // AddObjectContainerIDFilter adds filter by object container ID. func (r *Record) AddObjectContainerIDFilter(m Match, id cid.ID) { r.addObjectReservedFilter(m, fKeyObjContainerID, id) } // AddObjectOwnerIDFilter adds filter by object owner ID. func (r *Record) AddObjectOwnerIDFilter(m Match, id user.ID) { r.addObjectReservedFilter(m, fKeyObjOwnerID, id) } // AddObjectCreationEpoch adds filter by object creation epoch. func (r *Record) AddObjectCreationEpoch(m Match, epoch uint64) { r.addObjectReservedFilter(m, fKeyObjCreationEpoch, u64Stringer(epoch)) } // AddObjectPayloadLengthFilter adds filter by object payload length. func (r *Record) AddObjectPayloadLengthFilter(m Match, size uint64) { r.addObjectReservedFilter(m, fKeyObjPayloadLength, u64Stringer(size)) } // AddObjectPayloadHashFilter adds filter by object payload hash value. func (r *Record) AddObjectPayloadHashFilter(m Match, h checksum.Checksum) { r.addObjectReservedFilter(m, fKeyObjPayloadHash, staticStringer(h.String())) } // AddObjectTypeFilter adds filter by object type. func (r *Record) AddObjectTypeFilter(m Match, t object.Type) { r.addObjectReservedFilter(m, fKeyObjType, staticStringer(t.String())) } // AddObjectHomomorphicHashFilter adds filter by object payload homomorphic hash value. func (r *Record) AddObjectHomomorphicHashFilter(m Match, h checksum.Checksum) { r.addObjectReservedFilter(m, fKeyObjHomomorphicHash, staticStringer(h.String())) } // ToV2 converts Record to v2 acl.EACLRecord message. // // Nil Record converts to nil. func (r *Record) ToV2() *v2acl.Record { if r == nil { return nil } v2 := new(v2acl.Record) if r.targets != nil { targets := make([]v2acl.Target, len(r.targets)) for i := range r.targets { targets[i] = *r.targets[i].ToV2() } v2.SetTargets(targets) } if r.filters != nil { filters := make([]v2acl.HeaderFilter, len(r.filters)) for i := range r.filters { filters[i] = *r.filters[i].ToV2() } v2.SetFilters(filters) } v2.SetAction(r.action.ToV2()) v2.SetOperation(r.operation.ToV2()) return v2 } // NewRecord creates and returns blank Record instance. // // Defaults: // - action: ActionUnknown; // - operation: OperationUnknown; // - targets: nil, // - filters: nil. func NewRecord() *Record { return new(Record) } // CreateRecord creates, initializes with parameters and returns Record instance. func CreateRecord(action Action, operation Operation) *Record { r := NewRecord() r.action = action r.operation = operation r.targets = []Target{} r.filters = []Filter{} return r } // NewRecordFromV2 converts v2 acl.EACLRecord message to Record. func NewRecordFromV2(record *v2acl.Record) *Record { r := NewRecord() if record == nil { return r } r.action = ActionFromV2(record.GetAction()) r.operation = OperationFromV2(record.GetOperation()) v2targets := record.GetTargets() v2filters := record.GetFilters() r.targets = make([]Target, len(v2targets)) for i := range v2targets { r.targets[i] = *NewTargetFromV2(&v2targets[i]) } r.filters = make([]Filter, len(v2filters)) for i := range v2filters { r.filters[i] = *NewFilterFromV2(&v2filters[i]) } return r } // Marshal marshals Record into a protobuf binary form. func (r *Record) Marshal() ([]byte, error) { return r.ToV2().StableMarshal(nil), nil } // Unmarshal unmarshals protobuf binary representation of Record. func (r *Record) Unmarshal(data []byte) error { fV2 := new(v2acl.Record) if err := fV2.Unmarshal(data); err != nil { return err } *r = *NewRecordFromV2(fV2) return nil } // MarshalJSON encodes Record to protobuf JSON format. func (r *Record) MarshalJSON() ([]byte, error) { return r.ToV2().MarshalJSON() } // UnmarshalJSON decodes Record from protobuf JSON format. func (r *Record) UnmarshalJSON(data []byte) error { tV2 := new(v2acl.Record) if err := tV2.UnmarshalJSON(data); err != nil { return err } *r = *NewRecordFromV2(tV2) return nil } // equalRecords compares Record with each other. func equalRecords(r1, r2 Record) bool { if r1.Operation() != r2.Operation() || r1.Action() != r2.Action() { return false } fs1, fs2 := r1.Filters(), r2.Filters() ts1, ts2 := r1.Targets(), r2.Targets() if len(fs1) != len(fs2) || len(ts1) != len(ts2) { return false } for i := 0; i < len(fs1); i++ { if !equalFilters(fs1[i], fs2[i]) { return false } } for i := 0; i < len(ts1); i++ { if !equalTargets(ts1[i], ts2[i]) { return false } } return true }