[#225] bearer: Introduce APEOverride
field for the token
Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
dd23c6fd2b
commit
717a7d00ef
3 changed files with 177 additions and 2 deletions
114
bearer/bearer.go
114
bearer/bearer.go
|
@ -6,7 +6,9 @@ import (
|
|||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
apeV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/ape"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
apeSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
|
||||
frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
|
@ -33,9 +35,85 @@ type Token struct {
|
|||
sigSet bool
|
||||
sig refs.Signature
|
||||
|
||||
apeOverrideSet bool
|
||||
apeOverride APEOverride
|
||||
|
||||
impersonate bool
|
||||
}
|
||||
|
||||
// APEOverride is the list of APE chains defined for a target.
|
||||
// These chains are meant to serve as overrides to the already defined (or even undefined)
|
||||
// APE chains for the target (see contract `Policy`).
|
||||
//
|
||||
// The server-side processing of the bearer token with set APE overrides must verify if a client is permitted
|
||||
// to override chains for the target, preventing unauthorized access through the APE mechanism.
|
||||
type APEOverride struct {
|
||||
// Target for which chains are applied.
|
||||
Target apeSDK.ChainTarget
|
||||
|
||||
// The list of APE chains.
|
||||
Chains []apeSDK.Chain
|
||||
}
|
||||
|
||||
// Marshal marshals APEOverride into a protobuf binary form.
|
||||
func (c *APEOverride) Marshal() ([]byte, error) {
|
||||
return c.ToV2().StableMarshal(nil), nil
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals protobuf binary representation of APEOverride.
|
||||
func (c *APEOverride) Unmarshal(data []byte) error {
|
||||
overrideV2 := new(acl.APEOverride)
|
||||
if err := overrideV2.Unmarshal(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.FromV2(overrideV2)
|
||||
}
|
||||
|
||||
// MarshalJSON encodes APEOverride to protobuf JSON format.
|
||||
func (c *APEOverride) MarshalJSON() ([]byte, error) {
|
||||
return c.ToV2().MarshalJSON()
|
||||
}
|
||||
|
||||
// UnmarshalJSON decodes APEOverride from protobuf JSON format.
|
||||
func (c *APEOverride) UnmarshalJSON(data []byte) error {
|
||||
overrideV2 := new(acl.APEOverride)
|
||||
if err := overrideV2.UnmarshalJSON(data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.FromV2(overrideV2)
|
||||
}
|
||||
|
||||
func (c *APEOverride) FromV2(tokenAPEChains *acl.APEOverride) error {
|
||||
c.Target.FromV2(tokenAPEChains.GetTarget())
|
||||
if chains := tokenAPEChains.GetChains(); len(chains) > 0 {
|
||||
c.Chains = make([]apeSDK.Chain, len(chains))
|
||||
for i := range chains {
|
||||
if err := c.Chains[i].ReadFromV2(chains[i]); err != nil {
|
||||
return fmt.Errorf("invalid APE chain: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *APEOverride) ToV2() *acl.APEOverride {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
apeOverride := new(acl.APEOverride)
|
||||
apeOverride.SetTarget(c.Target.ToV2())
|
||||
chains := make([]*apeV2.Chain, len(c.Chains))
|
||||
for i := range c.Chains {
|
||||
chains[i] = c.Chains[i].ToV2()
|
||||
}
|
||||
apeOverride.SetChains(chains)
|
||||
|
||||
return apeOverride
|
||||
}
|
||||
|
||||
// reads Token from the acl.BearerToken message. If checkFieldPresence is set,
|
||||
// returns an error on absence of any protocol-required field.
|
||||
func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
|
||||
|
@ -48,10 +126,11 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
|
|||
|
||||
b.impersonate = body.GetImpersonate()
|
||||
|
||||
apeOverrides := body.GetAPEOverride()
|
||||
eaclTable := body.GetEACL()
|
||||
if b.eaclTableSet = eaclTable != nil; b.eaclTableSet {
|
||||
b.eaclTable = *eacl.NewTableFromV2(eaclTable)
|
||||
} else if checkFieldPresence && !b.impersonate {
|
||||
} else if checkFieldPresence && !b.impersonate && apeOverrides == nil {
|
||||
return errors.New("missing eACL table")
|
||||
}
|
||||
|
||||
|
@ -72,6 +151,14 @@ func (b *Token) readFromV2(m acl.BearerToken, checkFieldPresence bool) error {
|
|||
return errors.New("missing token lifetime")
|
||||
}
|
||||
|
||||
if b.apeOverrideSet = apeOverrides != nil; b.apeOverrideSet {
|
||||
if err = b.apeOverride.FromV2(apeOverrides); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if checkFieldPresence && !b.impersonate && !b.eaclTableSet {
|
||||
return errors.New("missing APE override")
|
||||
}
|
||||
|
||||
sig := m.GetSignature()
|
||||
if b.sigSet = sig != nil; sig != nil {
|
||||
b.sig = *sig
|
||||
|
@ -90,7 +177,7 @@ func (b *Token) ReadFromV2(m acl.BearerToken) error {
|
|||
}
|
||||
|
||||
func (b Token) fillBody() *acl.BearerTokenBody {
|
||||
if !b.eaclTableSet && !b.targetUserSet && !b.lifetimeSet && !b.impersonate {
|
||||
if !b.eaclTableSet && !b.targetUserSet && !b.lifetimeSet && !b.impersonate && !b.apeOverrideSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -116,6 +203,10 @@ func (b Token) fillBody() *acl.BearerTokenBody {
|
|||
body.SetLifetime(&lifetime)
|
||||
}
|
||||
|
||||
if b.apeOverrideSet {
|
||||
body.SetAPEOverride(b.apeOverride.ToV2())
|
||||
}
|
||||
|
||||
body.SetImpersonate(b.impersonate)
|
||||
|
||||
return &body
|
||||
|
@ -214,6 +305,25 @@ func (b Token) EACLTable() eacl.Table {
|
|||
return eacl.Table{}
|
||||
}
|
||||
|
||||
// SetAPEOverride sets APE override to the bearer token.
|
||||
//
|
||||
// See also: APEOverride.
|
||||
func (b *Token) SetAPEOverride(v APEOverride) {
|
||||
b.apeOverride = v
|
||||
b.apeOverrideSet = true
|
||||
}
|
||||
|
||||
// APEOverride returns APE override set by SetAPEOverride.
|
||||
//
|
||||
// Zero Token has zero APEOverride.
|
||||
func (b *Token) APEOverride() APEOverride {
|
||||
if b.apeOverrideSet {
|
||||
return b.apeOverride
|
||||
}
|
||||
|
||||
return APEOverride{}
|
||||
}
|
||||
|
||||
// SetImpersonate mark token as impersonate to consider token signer as request owner.
|
||||
// If this field is true extended EACLTable in token body isn't processed.
|
||||
func (b *Token) SetImpersonate(v bool) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package bearer_test
|
|||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
|
||||
|
@ -81,6 +82,58 @@ func TestToken_SetEACLTable(t *testing.T) {
|
|||
require.True(t, isEqualEACLTables(eaclTable, val.EACLTable()))
|
||||
}
|
||||
|
||||
func TestToken_SetAPEOverrides(t *testing.T) {
|
||||
var val bearer.Token
|
||||
var m acl.BearerToken
|
||||
filled := bearertest.Token()
|
||||
|
||||
val.WriteToV2(&m)
|
||||
require.Zero(t, m.GetBody())
|
||||
|
||||
val2 := filled
|
||||
|
||||
require.NoError(t, val2.Unmarshal(val.Marshal()))
|
||||
require.Zero(t, val2.APEOverride())
|
||||
|
||||
val2 = filled
|
||||
|
||||
jd, err := val.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, val2.UnmarshalJSON(jd))
|
||||
require.Zero(t, val2.APEOverride())
|
||||
|
||||
// set value
|
||||
|
||||
tApe := bearertest.APEOverride()
|
||||
|
||||
val.SetAPEOverride(tApe)
|
||||
require.Equal(t, tApe, val.APEOverride())
|
||||
|
||||
val.WriteToV2(&m)
|
||||
require.NotNil(t, m.GetBody().GetAPEOverride())
|
||||
require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), m.GetBody().GetAPEOverride()))
|
||||
|
||||
val2 = filled
|
||||
|
||||
require.NoError(t, val2.Unmarshal(val.Marshal()))
|
||||
apeOverride := val2.APEOverride()
|
||||
require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), apeOverride.ToV2()))
|
||||
|
||||
val2 = filled
|
||||
|
||||
jd, err = val.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, val2.UnmarshalJSON(jd))
|
||||
apeOverride = val.APEOverride()
|
||||
require.True(t, tokenAPEOverridesEqual(tApe.ToV2(), apeOverride.ToV2()))
|
||||
}
|
||||
|
||||
func tokenAPEOverridesEqual(lhs, rhs *acl.APEOverride) bool {
|
||||
return reflect.DeepEqual(lhs, rhs)
|
||||
}
|
||||
|
||||
func TestToken_ForUser(t *testing.T) {
|
||||
var val bearer.Token
|
||||
var m acl.BearerToken
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package bearertest
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
eacltest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl/test"
|
||||
usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test"
|
||||
|
@ -15,6 +16,17 @@ func Token() (t bearer.Token) {
|
|||
t.SetIat(1)
|
||||
t.ForUser(usertest.ID())
|
||||
t.SetEACLTable(*eacltest.Table())
|
||||
t.SetAPEOverride(APEOverride())
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func APEOverride() bearer.APEOverride {
|
||||
return bearer.APEOverride{
|
||||
Target: ape.ChainTarget{
|
||||
TargetType: ape.TargetTypeContainer,
|
||||
Name: "F8JsMnChywiPvbDvpxMbjTjx5KhWHHp6gCDt8BhzL9kF",
|
||||
},
|
||||
Chains: []ape.Chain{{Raw: []byte("{}")}},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue