diff --git a/CHANGELOG.md b/CHANGELOG.md
index e771c197..898dcce3 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 159c7e39..600586ec 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 d44e0570..4c768b2f 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 00000000..df8402a6
--- /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 00000000..c80c9cdb
--- /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 bcbd1998..24ecf281 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 f20423fb..7f3efc92 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 00000000..30c2ee3f
--- /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 00000000..b7dbbe05
--- /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 38f328be..f63758b1 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 50453b9f..796a4cd5 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 6f1e9136..3c54e8c9 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 75a5a0a8..785a30ae 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 e1caa068..7691220c 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
+}