diff --git a/CHANGELOG.md b/CHANGELOG.md index e771c19..898dcce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,23 @@ # Changelog This is the changelog for NeoFS-API-Go +## [1.2.0] - 2020-07-08 + +### Added + +- Extended ACL types. +- Getters and setters of ```EACLTable``` and its internal messages. +- Wrappers over ```EACLTable``` and its internal messages. +- Getters, setters and marshaling methods of wrappers. + +### Changed + +- Mechanism for signing requests on the principle of Matryoshka. + +### Updated + +- NeoFS API v1.1.0 => 1.2.0 + ## [1.1.0] - 2020-06-18 ### Added @@ -357,3 +374,4 @@ Initial public release [0.7.6]: https://github.com/nspcc-dev/neofs-api-go/compare/v0.7.5...v0.7.6 [1.0.0]: https://github.com/nspcc-dev/neofs-api-go/compare/v0.7.6...v1.0.0 [1.1.0]: https://github.com/nspcc-dev/neofs-api-go/compare/v1.0.0...v1.1.0 +[1.2.0]: https://github.com/nspcc-dev/neofs-api-go/compare/v1.1.0...v1.2.0 diff --git a/Makefile b/Makefile index 159c7e3..600586e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PROTO_VERSION=v1.1.0 +PROTO_VERSION=v1.2.0 PROTO_URL=https://github.com/nspcc-dev/neofs-api/archive/$(PROTO_VERSION).tar.gz B=\033[0;1m diff --git a/README.md b/README.md index d44e057..4c768b2 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,11 @@ can be used for integration with NeoFS. [neofs-api v1.1.0]: https://github.com/nspcc-dev/neofs-api/releases/tag/v1.1.0 [neofs-api-go v1.1.0]: https://github.com/nspcc-dev/neofs-api-go/releases/tag/v1.1.0 -[neofs-api-go v1.1.0] supports [neofs-api v1.1.0] +* [neofs-api-go v1.1.0] supports [neofs-api v1.1.0] + +[neofs-api v1.2.0]: https://github.com/nspcc-dev/neofs-api/releases/tag/v1.2.0 +[neofs-api-go v1.2.0]: https://github.com/nspcc-dev/neofs-api-go/releases/tag/v1.2.0 +* [neofs-api-go v1.2.0] supports [neofs-api v1.2.0] ## Description diff --git a/acl/extended.go b/acl/extended.go new file mode 100644 index 0000000..df8402a --- /dev/null +++ b/acl/extended.go @@ -0,0 +1,93 @@ +package acl + +// OperationType is an enumeration of operation types for extended ACL. +type OperationType uint32 + +// HeaderType is an enumeration of header types for extended ACL. +type HeaderType uint32 + +// MatchType is an enumeration of match types for extended ACL. +type MatchType uint32 + +// ExtendedACLAction is an enumeration of extended ACL actions. +type ExtendedACLAction uint32 + +// Header is an interface of string key-value pair, +type Header interface { + // Must return string identifier of header. + Name() string + + // Must return string value of header. + Value() string +} + +// TypedHeader is an interface of Header and HeaderType pair. +type TypedHeader interface { + Header + + // Must return type of filtered header. + HeaderType() HeaderType +} + +// HeaderFilter is an interface of grouped information about filtered header. +type HeaderFilter interface { + // Must return match type of filter. + MatchType() MatchType + + TypedHeader +} + +// ExtendedACLTarget is an interface of grouped information about extended ACL rule target. +type ExtendedACLTarget interface { + // Must return ACL target type. + Target() Target + + // Must return public key list of ACL targets. + KeyList() [][]byte +} + +// ExtendedACLRecord is an interface of record of extended ACL rule table. +type ExtendedACLRecord interface { + // Must return operation type of extended ACL rule. + OperationType() OperationType + + // Must return list of header filters of extended ACL rule. + HeaderFilters() []HeaderFilter + + // Must return target list of extended ACL rule. + TargetList() []ExtendedACLTarget + + // Must return action of extended ACL rule. + Action() ExtendedACLAction +} + +// ExtendedACLTable is an interface of extended ACL table. +type ExtendedACLTable interface { + // Must return list of extended ACL rules. + Records() []ExtendedACLRecord +} + +const ( + _ OperationType = iota + + // OpTypeGet is an OperationType for object.Get RPC + OpTypeGet + + // OpTypePut is an OperationType for object.Put RPC + OpTypePut + + // OpTypeHead is an OperationType for object.Head RPC + OpTypeHead + + // OpTypeSearch is an OperationType for object.Search RPC + OpTypeSearch + + // OpTypeDelete is an OperationType for object.Delete RPC + OpTypeDelete + + // OpTypeRange is an OperationType for object.GetRange RPC + OpTypeRange + + // OpTypeRangeHash is an OperationType for object.GetRangeHash RPC + OpTypeRangeHash +) diff --git a/acl/types.go b/acl/types.go new file mode 100644 index 0000000..c80c9cd --- /dev/null +++ b/acl/types.go @@ -0,0 +1,130 @@ +package acl + +const ( + _ MatchType = iota + + // StringEqual is a MatchType of string equality. + StringEqual + + // StringNotEqual is a MatchType of string inequality. + StringNotEqual +) + +const ( + // ActionUndefined is ExtendedACLAction used to mark value as undefined. + // Most of the tools consider ActionUndefined as incalculable. + // Using ActionUndefined in ExtendedACLRecord is unsafe. + ActionUndefined ExtendedACLAction = iota + + // ActionAllow is ExtendedACLAction used to mark an applicability of ACL rule. + ActionAllow + + // ActionDeny is ExtendedACLAction used to mark an inapplicability of ACL rule. + ActionDeny +) + +const ( + _ HeaderType = iota + + // HdrTypeRequest is a HeaderType for request header. + HdrTypeRequest + + // HdrTypeObjSys is a HeaderType for system headers of object. + HdrTypeObjSys + + // HdrTypeObjUsr is a HeaderType for user headers of object. + HdrTypeObjUsr +) + +const ( + // HdrObjSysNameID is a name of ID field in system header of object. + HdrObjSysNameID = "ID" + + // HdrObjSysNameCID is a name of CID field in system header of object. + HdrObjSysNameCID = "CID" + + // HdrObjSysNameOwnerID is a name of OwnerID field in system header of object. + HdrObjSysNameOwnerID = "OWNER_ID" + + // HdrObjSysNameVersion is a name of Version field in system header of object. + HdrObjSysNameVersion = "VERSION" + + // HdrObjSysNamePayloadLength is a name of PayloadLength field in system header of object. + HdrObjSysNamePayloadLength = "PAYLOAD_LENGTH" + + // HdrObjSysNameCreatedUnix is a name of CreatedAt.UnitTime field in system header of object. + HdrObjSysNameCreatedUnix = "CREATED_UNIX" + + // HdrObjSysNameCreatedEpoch is a name of CreatedAt.Epoch field in system header of object. + HdrObjSysNameCreatedEpoch = "CREATED_EPOCH" + + // HdrObjSysLinkPrev is a name of previous link header in extended headers of object. + HdrObjSysLinkPrev = "LINK_PREV" + + // HdrObjSysLinkNext is a name of next link header in extended headers of object. + HdrObjSysLinkNext = "LINK_NEXT" + + // HdrObjSysLinkChild is a name of child link header in extended headers of object. + HdrObjSysLinkChild = "LINK_CHILD" + + // HdrObjSysLinkPar is a name of parent link header in extended headers of object. + HdrObjSysLinkPar = "LINK_PAR" + + // HdrObjSysLinkSG is a name of storage group link header in extended headers of object. + HdrObjSysLinkSG = "LINK_SG" +) + +// SetMatchType is MatchType field setter. +func (m *EACLRecord_FilterInfo) SetMatchType(v EACLRecord_FilterInfo_MatchType) { + m.MatchType = v +} + +// SetHeader is a Header field setter. +func (m *EACLRecord_FilterInfo) SetHeader(v EACLRecord_FilterInfo_Header) { + m.Header = v +} + +// SetHeaderName is a HeaderName field setter. +func (m *EACLRecord_FilterInfo) SetHeaderName(v string) { + m.HeaderName = v +} + +// SetHeaderVal is a HeaderVal field setter. +func (m *EACLRecord_FilterInfo) SetHeaderVal(v string) { + m.HeaderVal = v +} + +// SetTarget is a Target field setter. +func (m *EACLRecord_TargetInfo) SetTarget(v Target) { + m.Target = v +} + +// SetKeyList is a KeyList field setter. +func (m *EACLRecord_TargetInfo) SetKeyList(v [][]byte) { + m.KeyList = v +} + +// SetOperation is an Operation field setter. +func (m *EACLRecord) SetOperation(v EACLRecord_Operation) { + m.Operation = v +} + +// SetAction is an Action field setter. +func (m *EACLRecord) SetAction(v EACLRecord_Action) { + m.Action = v +} + +// SetFilters is a Filters field setter. +func (m *EACLRecord) SetFilters(v []*EACLRecord_FilterInfo) { + m.Filters = v +} + +// SetTargets is a Targets field setter. +func (m *EACLRecord) SetTargets(v []*EACLRecord_TargetInfo) { + m.Targets = v +} + +// SetRecords is a Records field setter. +func (m *EACLTable) SetRecords(v []*EACLRecord) { + m.Records = v +} diff --git a/acl/types.pb.go b/acl/types.pb.go index bcbd199..24ecf28 100644 Binary files a/acl/types.pb.go and b/acl/types.pb.go differ diff --git a/acl/types.proto b/acl/types.proto index f20423f..7f3efc9 100644 --- a/acl/types.proto +++ b/acl/types.proto @@ -25,3 +25,82 @@ enum Target { // extended ACL. PubKey = 4; } + +// EACLRecord groups information about extended ACL rule. +message EACLRecord { + // Operation is an enumeration of operation types. + enum Operation { + OPERATION_UNKNOWN = 0; + GET = 1; + HEAD = 2; + PUT = 3; + DELETE = 4; + SEARCH = 5; + GETRANGE = 6; + GETRANGEHASH = 7; + } + + // Operation carries type of operation. + Operation operation = 1 [(gogoproto.customname) = "Operation", json_name="Operation"]; + + // Action is an enumeration of EACL actions. + enum Action { + ActionUnknown = 0; + Allow = 1; + Deny = 2; + } + + // Action carries ACL target action. + Action action = 2 [(gogoproto.customname) = "Action", json_name="Action"]; + + // FilterInfo groups information about filter. + message FilterInfo { + // Header is an enumeration of filtering header types. + enum Header { + HeaderUnknown = 0; + Request = 1; + ObjectSystem = 2; + ObjectUser = 3; + } + + // Header carries type of header. + Header header = 1 [(gogoproto.customname) = "Header", json_name="HeaderType"]; + + // MatchType is an enumeration of match types. + enum MatchType { + MatchUnknown = 0; + StringEqual = 1; + StringNotEqual = 2; + } + + // MatchType carries type of match. + MatchType matchType = 2 [(gogoproto.customname) = "MatchType", json_name="MatchType"]; + + // HeaderName carries name of filtering header. + string HeaderName = 3 [json_name="Name"]; + + // HeaderVal carries value of filtering header. + string HeaderVal = 4 [json_name="Value"]; + } + + // Filters carries set of filters. + repeated FilterInfo Filters = 3 [json_name="Filters"]; + + // TargetInfo groups information about extended ACL target. + message TargetInfo { + // Target carries target of ACL rule. + acl.Target Target = 1 [json_name="Role"]; + + // KeyList carries public keys of ACL target. + repeated bytes KeyList = 2 [json_name="Keys"]; + } + + // Targets carries information about extended ACL target list. + repeated TargetInfo Targets = 4 [json_name="Targets"]; +} + +// EACLRecord carries the information about extended ACL rules. +message EACLTable { + // Records carries list of extended ACL rule records. + repeated EACLRecord Records = 1 [json_name="Records"]; +} diff --git a/acl/wrappers.go b/acl/wrappers.go new file mode 100644 index 0000000..30c2ee3 --- /dev/null +++ b/acl/wrappers.go @@ -0,0 +1,498 @@ +package acl + +// EACLFilterWrapper is a wrapper over EACLRecord_FilterInfo pointer. +type EACLFilterWrapper struct { + filter *EACLRecord_FilterInfo +} + +// EACLTargetWrapper is a wrapper over EACLRecord_TargetInfo pointer. +type EACLTargetWrapper struct { + target *EACLRecord_TargetInfo +} + +// EACLRecordWrapper is a wrapper over EACLRecord pointer. +type EACLRecordWrapper struct { + record *EACLRecord +} + +// EACLTableWrapper is a wrapper over EACLTable pointer. +type EACLTableWrapper struct { + table *EACLTable +} + +// WrapFilterInfo wraps EACLRecord_FilterInfo pointer. +// +// If argument is nil, new EACLRecord_FilterInfo is initialized. +func WrapFilterInfo(v *EACLRecord_FilterInfo) EACLFilterWrapper { + if v == nil { + v = new(EACLRecord_FilterInfo) + } + + return EACLFilterWrapper{ + filter: v, + } +} + +// WrapEACLTarget wraps EACLRecord_TargetInfo pointer. +// +// If argument is nil, new EACLRecord_TargetInfo is initialized. +func WrapEACLTarget(v *EACLRecord_TargetInfo) EACLTargetWrapper { + if v == nil { + v = new(EACLRecord_TargetInfo) + } + + return EACLTargetWrapper{ + target: v, + } +} + +// WrapEACLRecord wraps EACLRecord pointer. +// +// If argument is nil, new EACLRecord is initialized. +func WrapEACLRecord(v *EACLRecord) EACLRecordWrapper { + if v == nil { + v = new(EACLRecord) + } + + return EACLRecordWrapper{ + record: v, + } +} + +// WrapEACLTable wraps EACLTable pointer. +// +// If argument is nil, new EACLTable is initialized. +func WrapEACLTable(v *EACLTable) EACLTableWrapper { + if v == nil { + v = new(EACLTable) + } + + return EACLTableWrapper{ + table: v, + } +} + +// MatchType returns casted result of MatchType field getter. +// +// If filter is not initialized, 0 returns. +// +// Returns 0 if MatchType is not one of: +// - EACLRecord_FilterInfo_StringEqual; +// - EACLRecord_FilterInfo_StringNotEqual. +func (s EACLFilterWrapper) MatchType() (res MatchType) { + if s.filter != nil { + switch s.filter.GetMatchType() { + case EACLRecord_FilterInfo_StringEqual: + res = StringEqual + case EACLRecord_FilterInfo_StringNotEqual: + res = StringNotEqual + } + } + + return +} + +// SetMatchType passes casted argument to MatchType field setter. +// +// If filter is not initialized, nothing changes. +// +// MatchType is set to EACLRecord_FilterInfo_MatchUnknown if argument is not one of: +// - StringEqual; +// - StringNotEqual. +func (s EACLFilterWrapper) SetMatchType(v MatchType) { + if s.filter != nil { + switch v { + case StringEqual: + s.filter.SetMatchType(EACLRecord_FilterInfo_StringEqual) + case StringNotEqual: + s.filter.SetMatchType(EACLRecord_FilterInfo_StringNotEqual) + default: + s.filter.SetMatchType(EACLRecord_FilterInfo_MatchUnknown) + } + } +} + +// Name returns the result of HeaderName field getter. +// +// If filter is not initialized, empty string returns. +func (s EACLFilterWrapper) Name() string { + if s.filter == nil { + return "" + } + + return s.filter.GetHeaderName() +} + +// SetName passes argument to HeaderName field setter. +// +// If filter is not initialized, nothing changes. +func (s EACLFilterWrapper) SetName(v string) { + if s.filter != nil { + s.filter.SetHeaderName(v) + } +} + +// Value returns the result of HeaderVal field getter. +// +// If filter is not initialized, empty string returns. +func (s EACLFilterWrapper) Value() string { + if s.filter == nil { + return "" + } + + return s.filter.GetHeaderVal() +} + +// SetValue passes argument to HeaderVal field setter. +// +// If filter is not initialized, nothing changes. +func (s EACLFilterWrapper) SetValue(v string) { + if s.filter != nil { + s.filter.SetHeaderVal(v) + } +} + +// HeaderType returns the result of Header field getter. +// +// If filter is not initialized, 0 returns. +// +// Returns 0 if Header is not one of: +// - EACLRecord_FilterInfo_Request; +// - EACLRecord_FilterInfo_ObjectSystem; +// - EACLRecord_FilterInfo_ObjectUser. +func (s EACLFilterWrapper) HeaderType() (res HeaderType) { + if s.filter != nil { + switch s.filter.GetHeader() { + case EACLRecord_FilterInfo_Request: + res = HdrTypeRequest + case EACLRecord_FilterInfo_ObjectSystem: + res = HdrTypeObjSys + case EACLRecord_FilterInfo_ObjectUser: + res = HdrTypeObjUsr + } + } + + return +} + +// SetHeaderType passes casted argument to Header field setter. +// +// If filter is not initialized, nothing changes. +// +// Header is set to EACLRecord_FilterInfo_HeaderUnknown if argument is not one of: +// - HdrTypeRequest; +// - HdrTypeObjSys; +// - HdrTypeObjUsr. +func (s EACLFilterWrapper) SetHeaderType(t HeaderType) { + if s.filter != nil { + switch t { + case HdrTypeRequest: + s.filter.SetHeader(EACLRecord_FilterInfo_Request) + case HdrTypeObjSys: + s.filter.SetHeader(EACLRecord_FilterInfo_ObjectSystem) + case HdrTypeObjUsr: + s.filter.SetHeader(EACLRecord_FilterInfo_ObjectUser) + default: + s.filter.SetHeader(EACLRecord_FilterInfo_HeaderUnknown) + } + } +} + +// Target returns the result of Target field getter. +// +// If target is not initialized, Target_Unknown returns. +func (s EACLTargetWrapper) Target() Target { + if s.target == nil { + return Target_Unknown + } + + return s.target.GetTarget() +} + +// SetTarget passes argument to Target field setter. +// +// If target is not initialized, nothing changes. +func (s EACLTargetWrapper) SetTarget(v Target) { + if s.target != nil { + s.target.SetTarget(v) + } +} + +// KeyList returns the result of KeyList field getter. +// +// If target is not initialized, nil returns. +func (s EACLTargetWrapper) KeyList() [][]byte { + if s.target == nil { + return nil + } + + return s.target.GetKeyList() +} + +// SetKeyList passes argument to KeyList field setter. +// +// If target is not initialized, nothing changes. +func (s EACLTargetWrapper) SetKeyList(v [][]byte) { + if s.target != nil { + s.target.SetKeyList(v) + } +} + +// OperationType returns casted result of Operation field getter. +// +// If record is not initialized, 0 returns. +// +// Returns 0 if Operation is not one of: +// - EACLRecord_HEAD; +// - EACLRecord_PUT; +// - EACLRecord_SEARCH; +// - EACLRecord_GET; +// - EACLRecord_GETRANGE; +// - EACLRecord_GETRANGEHASH; +// - EACLRecord_DELETE. +func (s EACLRecordWrapper) OperationType() (res OperationType) { + if s.record != nil { + switch s.record.GetOperation() { + case EACLRecord_HEAD: + res = OpTypeHead + case EACLRecord_PUT: + res = OpTypePut + case EACLRecord_SEARCH: + res = OpTypeSearch + case EACLRecord_GET: + res = OpTypeGet + case EACLRecord_GETRANGE: + res = OpTypeRange + case EACLRecord_GETRANGEHASH: + res = OpTypeRangeHash + case EACLRecord_DELETE: + res = OpTypeDelete + } + } + + return +} + +// SetOperationType passes casted argument to Operation field setter. +// +// If record is not initialized, nothing changes. +// +// Operation is set to EACLRecord_OPERATION_UNKNOWN if argument is not one of: +// - OpTypeHead; +// - OpTypePut; +// - OpTypeSearch; +// - OpTypeGet; +// - OpTypeRange; +// - OpTypeRangeHash; +// - OpTypeDelete. +func (s EACLRecordWrapper) SetOperationType(v OperationType) { + if s.record != nil { + switch v { + case OpTypeHead: + s.record.SetOperation(EACLRecord_HEAD) + case OpTypePut: + s.record.SetOperation(EACLRecord_PUT) + case OpTypeSearch: + s.record.SetOperation(EACLRecord_SEARCH) + case OpTypeGet: + s.record.SetOperation(EACLRecord_GET) + case OpTypeRange: + s.record.SetOperation(EACLRecord_GETRANGE) + case OpTypeRangeHash: + s.record.SetOperation(EACLRecord_GETRANGEHASH) + case OpTypeDelete: + s.record.SetOperation(EACLRecord_DELETE) + default: + s.record.SetOperation(EACLRecord_OPERATION_UNKNOWN) + } + } +} + +// Action returns casted result of Action field getter. +// +// If record is not initialized, 0 returns. +// +// Returns 0 if Action is not one of: +// - EACLRecord_Deny; +// - EACLRecord_Allow. +func (s EACLRecordWrapper) Action() (res ExtendedACLAction) { + if s.record != nil { + switch s.record.GetAction() { + case EACLRecord_Deny: + res = ActionDeny + case EACLRecord_Allow: + res = ActionAllow + } + } + + return +} + +// SetAction passes casted argument to Action field setter. +// +// If record is not initialized, nothing changes. +// +// Action is set to EACLRecord_ActionUnknown if argument is not one of: +// - ActionDeny; +// - ActionAllow. +func (s EACLRecordWrapper) SetAction(v ExtendedACLAction) { + if s.record != nil { + switch v { + case ActionDeny: + s.record.SetAction(EACLRecord_Deny) + case ActionAllow: + s.record.SetAction(EACLRecord_Allow) + default: + s.record.SetAction(EACLRecord_ActionUnknown) + } + } +} + +// HeaderFilters wraps all elements from Filters field getter result and returns HeaderFilter list. +// +// If record is not initialized, nil returns. +func (s EACLRecordWrapper) HeaderFilters() []HeaderFilter { + if s.record == nil { + return nil + } + + filters := s.record.GetFilters() + + res := make([]HeaderFilter, 0, len(filters)) + + for i := range filters { + res = append(res, WrapFilterInfo(filters[i])) + } + + return res +} + +// SetHeaderFilters converts HeaderFilter list to EACLRecord_FilterInfo list and passes it to Filters field setter. +// +// Ignores nil elements of argument. +// If record is not initialized, nothing changes. +func (s EACLRecordWrapper) SetHeaderFilters(v []HeaderFilter) { + if s.record == nil { + return + } + + filters := make([]*EACLRecord_FilterInfo, 0, len(v)) + + for i := range v { + if v[i] == nil { + continue + } + + w := WrapFilterInfo(nil) + w.SetMatchType(v[i].MatchType()) + w.SetHeaderType(v[i].HeaderType()) + w.SetName(v[i].Name()) + w.SetValue(v[i].Value()) + + filters = append(filters, w.filter) + } + + s.record.SetFilters(filters) +} + +// TargetList wraps all elements from Targets field getter result and returns ExtendedACLTarget list. +// +// If record is not initialized, nil returns. +func (s EACLRecordWrapper) TargetList() []ExtendedACLTarget { + if s.record == nil { + return nil + } + + targets := s.record.GetTargets() + + res := make([]ExtendedACLTarget, 0, len(targets)) + + for i := range targets { + res = append(res, WrapEACLTarget(targets[i])) + } + + return res +} + +// SetTargetList converts ExtendedACLTarget list to EACLRecord_TargetInfo list and passes it to Targets field setter. +// +// Ignores nil elements of argument. +// If record is not initialized, nothing changes. +func (s EACLRecordWrapper) SetTargetList(v []ExtendedACLTarget) { + if s.record == nil { + return + } + + targets := make([]*EACLRecord_TargetInfo, 0, len(v)) + + for i := range v { + if v[i] == nil { + continue + } + + w := WrapEACLTarget(nil) + w.SetTarget(v[i].Target()) + w.SetKeyList(v[i].KeyList()) + + targets = append(targets, w.target) + } + + s.record.SetTargets(targets) +} + +// Records wraps all elements from Records field getter result and returns ExtendedACLRecord list. +// +// If table is not initialized, nil returns. +func (s EACLTableWrapper) Records() []ExtendedACLRecord { + if s.table == nil { + return nil + } + + records := s.table.GetRecords() + + res := make([]ExtendedACLRecord, 0, len(records)) + + for i := range records { + res = append(res, WrapEACLRecord(records[i])) + } + + return res +} + +// SetRecords converts ExtendedACLRecord list to EACLRecord list and passes it to Records field setter. +// +// Ignores nil elements of argument. +// If table is not initialized, nothing changes. +func (s EACLTableWrapper) SetRecords(v []ExtendedACLRecord) { + if s.table == nil { + return + } + + records := make([]*EACLRecord, 0, len(v)) + + for i := range v { + if v[i] == nil { + continue + } + + w := WrapEACLRecord(nil) + w.SetOperationType(v[i].OperationType()) + w.SetAction(v[i].Action()) + w.SetHeaderFilters(v[i].HeaderFilters()) + w.SetTargetList(v[i].TargetList()) + + records = append(records, w.record) + } + + s.table.SetRecords(records) +} + +// MarshalBinary returns the result of Marshal method. +func (s EACLTableWrapper) MarshalBinary() ([]byte, error) { + return s.table.Marshal() +} + +// UnmarshalBinary passes argument to Unmarshal method and returns its result. +func (s EACLTableWrapper) UnmarshalBinary(data []byte) error { + return s.table.Unmarshal(data) +} diff --git a/acl/wrappers_test.go b/acl/wrappers_test.go new file mode 100644 index 0000000..b7dbbe0 --- /dev/null +++ b/acl/wrappers_test.go @@ -0,0 +1,139 @@ +package acl + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEACLFilterWrapper(t *testing.T) { + s := WrapFilterInfo(nil) + + mt := StringEqual + s.SetMatchType(mt) + require.Equal(t, mt, s.MatchType()) + + ht := HdrTypeObjUsr + s.SetHeaderType(ht) + require.Equal(t, ht, s.HeaderType()) + + n := "name" + s.SetName(n) + require.Equal(t, n, s.Name()) + + v := "value" + s.SetValue(v) + require.Equal(t, v, s.Value()) +} + +func TestEACLTargetWrapper(t *testing.T) { + s := WrapEACLTarget(nil) + + target := Target(10) + s.SetTarget(target) + require.Equal(t, target, s.Target()) + + keys := [][]byte{ + {1, 2, 3}, + {4, 5, 6}, + } + s.SetKeyList(keys) + require.Equal(t, keys, s.KeyList()) +} + +func TestEACLRecordWrapper(t *testing.T) { + s := WrapEACLRecord(nil) + + action := ActionAllow + s.SetAction(action) + require.Equal(t, action, s.Action()) + + opType := OperationType(5) + s.SetOperationType(opType) + require.Equal(t, opType, s.OperationType()) + + f1Name := "name1" + f1 := WrapFilterInfo(nil) + f1.SetName(f1Name) + + f2Name := "name2" + f2 := WrapFilterInfo(nil) + f2.SetName(f2Name) + + s.SetHeaderFilters([]HeaderFilter{f1, f2}) + + filters := s.HeaderFilters() + require.Len(t, filters, 2) + require.Equal(t, f1Name, filters[0].Name()) + require.Equal(t, f2Name, filters[1].Name()) + + target1 := Target(1) + t1 := WrapEACLTarget(nil) + t1.SetTarget(target1) + + target2 := Target(2) + t2 := WrapEACLTarget(nil) + t2.SetTarget(target2) + + s.SetTargetList([]ExtendedACLTarget{t1, t2}) + + targets := s.TargetList() + require.Len(t, targets, 2) + require.Equal(t, target1, targets[0].Target()) + require.Equal(t, target2, targets[1].Target()) +} + +func TestEACLTableWrapper(t *testing.T) { + s := WrapEACLTable(nil) + + action1 := ExtendedACLAction(1) + r1 := WrapEACLRecord(nil) + r1.SetAction(action1) + + action2 := ExtendedACLAction(2) + r2 := WrapEACLRecord(nil) + r2.SetAction(action2) + + s.SetRecords([]ExtendedACLRecord{r1, r2}) + + records := s.Records() + require.Len(t, records, 2) + require.Equal(t, action1, records[0].Action()) + require.Equal(t, action2, records[1].Action()) + + data, err := s.MarshalBinary() + require.NoError(t, err) + + s2 := WrapEACLTable(nil) + require.NoError(t, s2.UnmarshalBinary(data)) + + records1 := s.Records() + records2 := s2.Records() + require.Len(t, records1, len(records2)) + + for i := range records1 { + require.Equal(t, records1[i].Action(), records2[i].Action()) + require.Equal(t, records1[i].OperationType(), records2[i].OperationType()) + + targets1 := records1[i].TargetList() + targets2 := records2[i].TargetList() + require.Len(t, targets1, len(targets2)) + + for j := range targets1 { + require.Equal(t, targets1[j].Target(), targets2[j].Target()) + require.Equal(t, targets1[j].KeyList(), targets2[j].KeyList()) + } + + filters1 := records1[i].HeaderFilters() + filters2 := records2[i].HeaderFilters() + require.Len(t, filters1, len(filters2)) + + for j := range filters1 { + require.Equal(t, filters1[j].MatchType(), filters2[j].MatchType()) + require.Equal(t, filters1[j].HeaderType(), filters2[j].HeaderType()) + require.Equal(t, filters1[j].Name(), filters2[j].Name()) + require.Equal(t, filters1[j].Value(), filters2[j].Value()) + require.Equal(t, filters1[j].Value(), filters2[j].Value()) + } + } +} diff --git a/docs/acl.md b/docs/acl.md index 38f328b..f63758b 100644 --- a/docs/acl.md +++ b/docs/acl.md @@ -5,7 +5,12 @@ - [acl/types.proto](#acl/types.proto) - + - Messages + - [EACLRecord](#acl.EACLRecord) + - [EACLRecord.FilterInfo](#acl.EACLRecord.FilterInfo) + - [EACLRecord.TargetInfo](#acl.EACLRecord.TargetInfo) + - [EACLTable](#acl.EACLTable) + - [Scalar Value Types](#scalar-value-types) @@ -19,9 +24,118 @@ + + + +### Message EACLRecord +EACLRecord groups information about extended ACL rule. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| operation | [EACLRecord.Operation](#acl.EACLRecord.Operation) | | Operation carries type of operation. | +| action | [EACLRecord.Action](#acl.EACLRecord.Action) | | Action carries ACL target action. | +| Filters | [EACLRecord.FilterInfo](#acl.EACLRecord.FilterInfo) | repeated | Filters carries set of filters. | +| Targets | [EACLRecord.TargetInfo](#acl.EACLRecord.TargetInfo) | repeated | Targets carries information about extended ACL target list. | + + + + +### Message EACLRecord.FilterInfo +FilterInfo groups information about filter. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| header | [EACLRecord.FilterInfo.Header](#acl.EACLRecord.FilterInfo.Header) | | Header carries type of header. | +| matchType | [EACLRecord.FilterInfo.MatchType](#acl.EACLRecord.FilterInfo.MatchType) | | MatchType carries type of match. | +| HeaderName | [string](#string) | | HeaderName carries name of filtering header. | +| HeaderVal | [string](#string) | | HeaderVal carries value of filtering header. | + + + + +### Message EACLRecord.TargetInfo +TargetInfo groups information about extended ACL target. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Target | [Target](#acl.Target) | | Target carries target of ACL rule. | +| KeyList | [bytes](#bytes) | repeated | KeyList carries public keys of ACL target. | + + + + +### Message EACLTable +EACLRecord carries the information about extended ACL rules. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| Records | [EACLRecord](#acl.EACLRecord) | repeated | Records carries list of extended ACL rule records. | + + + +### EACLRecord.Action +Action is an enumeration of EACL actions. + +| Name | Number | Description | +| ---- | ------ | ----------- | +| ActionUnknown | 0 | | +| Allow | 1 | | +| Deny | 2 | | + + + + + +### EACLRecord.FilterInfo.Header +Header is an enumeration of filtering header types. + +| Name | Number | Description | +| ---- | ------ | ----------- | +| HeaderUnknown | 0 | | +| Request | 1 | | +| ObjectSystem | 2 | | +| ObjectUser | 3 | | + + + + + +### EACLRecord.FilterInfo.MatchType +MatchType is an enumeration of match types. + +| Name | Number | Description | +| ---- | ------ | ----------- | +| MatchUnknown | 0 | | +| StringEqual | 1 | | +| StringNotEqual | 2 | | + + + + + +### EACLRecord.Operation +Operation is an enumeration of operation types. + +| Name | Number | Description | +| ---- | ------ | ----------- | +| OPERATION_UNKNOWN | 0 | | +| GET | 1 | | +| HEAD | 2 | | +| PUT | 3 | | +| DELETE | 4 | | +| SEARCH | 5 | | +| GETRANGE | 6 | | +| GETRANGEHASH | 7 | | + + + ### Target diff --git a/service/sign.go b/service/sign.go index 50453b9..796a4cd 100644 --- a/service/sign.go +++ b/service/sign.go @@ -137,11 +137,24 @@ func verifySignatures(src SignedDataSource, items ...SignKeyPair) error { } defer bytesPool.Put(data) - for _, signKey := range items { + for i := range items { + if i > 0 { + // add previous key bytes to the signed message + + signKeyDataSrc := SignKeyPairsSignedData(items[i-1]) + + signKeyData, err := signKeyDataSrc.SignedData() + if err != nil { + return errors.Wrapf(err, "could not get signed data of key-signature #%d", i) + } + + data = append(data, signKeyData...) + } + if err := crypto.Verify( - signKey.GetPublicKey(), + items[i].GetPublicKey(), data, - signKey.GetSignature(), + items[i].GetSignature(), ); err != nil { return err } @@ -213,6 +226,7 @@ func SignRequestData(key *ecdsa.PrivateKey, src RequestSignedData) error { src.GetBearerToken(), ), ExtendedHeadersSignedData(src), + SignKeyPairsSignedData(src.GetSignKeyPairs()...), ) if err != nil { return err diff --git a/service/sign_test.go b/service/sign_test.go index 6f1e913..3c54e8c 100644 --- a/service/sign_test.go +++ b/service/sign_test.go @@ -15,13 +15,13 @@ import ( type testSignedDataSrc struct { err error data []byte - sig []byte - key *ecdsa.PublicKey token SessionToken bearer BearerToken extHdrs []ExtendedHeader + + signKeys []SignKeyPair } type testSignedDataReader struct { @@ -29,13 +29,15 @@ type testSignedDataReader struct { } func (s testSignedDataSrc) GetSignature() []byte { - return s.sig + if len(s.signKeys) > 0 { + return s.signKeys[0].GetSignature() + } + + return nil } func (s testSignedDataSrc) GetSignKeyPairs() []SignKeyPair { - return []SignKeyPair{ - newSignatureKeyPair(s.key, s.sig), - } + return s.signKeys } func (s testSignedDataSrc) SignedData() ([]byte, error) { @@ -43,8 +45,9 @@ func (s testSignedDataSrc) SignedData() ([]byte, error) { } func (s *testSignedDataSrc) AddSignKey(sig []byte, key *ecdsa.PublicKey) { - s.key = key - s.sig = sig + s.signKeys = append(s.signKeys, + newSignatureKeyPair(key, sig), + ) } func testData(t *testing.T, sz int) []byte { @@ -209,23 +212,27 @@ func TestVerifyAccumulatedSignatures(t *testing.T) { // create test private key sk := test.DecodeKey(0) + signKey := new(RequestVerificationHeader_Signature) + signKey.Peer = crypto.MarshalPublicKey(&sk.PublicKey) + // create signature source src := &testSignedDataSrc{ data: testData(t, 10), - key: &sk.PublicKey, + + signKeys: []SignKeyPair{signKey}, } var err error // calculate a signature - src.sig, err = crypto.Sign(sk, src.data) + signKey.Sign, err = crypto.Sign(sk, src.data) require.NoError(t, err) // ascertain that verification is passed require.NoError(t, VerifyAccumulatedSignatures(src)) // break the signature - src.sig[0]++ + signKey.Sign[0]++ // ascertain that verification is failed require.Error(t, VerifyAccumulatedSignatures(src)) @@ -238,9 +245,13 @@ func TestVerifySignatureWithKey(t *testing.T) { ErrEmptyDataWithSignature.Error(), ) + signKey := new(RequestVerificationHeader_Signature) + // create test signature source src := &testSignedDataSrc{ data: testData(t, 10), + + signKeys: []SignKeyPair{signKey}, } // nil public key @@ -255,14 +266,14 @@ func TestVerifySignatureWithKey(t *testing.T) { var err error // calculate a signature - src.sig, err = crypto.Sign(sk, src.data) + signKey.Sign, err = crypto.Sign(sk, src.data) require.NoError(t, err) // ascertain that verification is passed require.NoError(t, VerifySignatureWithKey(&sk.PublicKey, src)) // break the signature - src.sig[0]++ + signKey.Sign[0]++ // ascertain that verification is failed require.Error(t, VerifySignatureWithKey(&sk.PublicKey, src)) @@ -375,4 +386,15 @@ func TestSignVerifyRequestData(t *testing.T) { // ascertain that verification is passed require.NoError(t, VerifyRequestData(rdr)) + + if len(rdr.GetSignKeyPairs()) < 2 { + // add one more signature + require.NoError(t, SignRequestData(test.DecodeKey(1), rdr)) + } + + // change key-signature order + rdr.signKeys[0], rdr.signKeys[1] = rdr.signKeys[1], rdr.signKeys[0] + + // ascertain that verification is failed + require.Error(t, VerifyRequestData(src)) } diff --git a/service/types.go b/service/types.go index 75a5a0a..785a30a 100644 --- a/service/types.go +++ b/service/types.go @@ -262,6 +262,7 @@ type RequestData interface { type RequestSignedData interface { RequestData SignKeyPairAccumulator + SignKeyPairSource } // RequestVerifyData is an interface of request information with signature read access. diff --git a/service/verify.go b/service/verify.go index e1caa06..7691220 100644 --- a/service/verify.go +++ b/service/verify.go @@ -2,11 +2,16 @@ package service import ( "crypto/ecdsa" + "io" "github.com/nspcc-dev/neofs-api-go/internal" crypto "github.com/nspcc-dev/neofs-crypto" ) +type signKeyPairsWrapper struct { + items []SignKeyPair +} + // GetSessionToken returns SessionToken interface of Token field. // // If token field value is nil, nil returns. @@ -114,3 +119,48 @@ func (m RequestVerificationHeader) GetBearerToken() BearerToken { return nil } + +// SignKeyPairsSignedData wraps passed SignKeyPair slice and returns SignedDataSource interface. +func SignKeyPairsSignedData(v ...SignKeyPair) SignedDataSource { + return &signKeyPairsWrapper{ + items: v, + } +} + +// SignedData returns signed SignKeyPair slice in a binary representation. +func (s signKeyPairsWrapper) SignedData() ([]byte, error) { + return SignedDataFromReader(s) +} + +// SignedDataSize returns the length of signed SignKeyPair slice. +func (s signKeyPairsWrapper) SignedDataSize() (sz int) { + for i := range s.items { + // add key length + sz += len( + crypto.MarshalPublicKey(s.items[i].GetPublicKey()), + ) + } + + return +} + +// ReadSignedData copies a binary representation of the signed SignKeyPair slice to passed buffer. +// +// If buffer length is less than required, io.ErrUnexpectedEOF returns. +func (s signKeyPairsWrapper) ReadSignedData(p []byte) (int, error) { + sz := s.SignedDataSize() + if len(p) < sz { + return 0, io.ErrUnexpectedEOF + } + + off := 0 + + for i := range s.items { + // copy public key bytes + off += copy(p[off:], crypto.MarshalPublicKey( + s.items[i].GetPublicKey(), + )) + } + + return off, nil +}