mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 12:26:34 +02:00
* base * unit tests * group tests * groups test * entity test * alias test and fix error code * fix error message * lint --------- Co-authored-by: miagilepner <mia.epner@hashicorp.com> Co-authored-by: Kuba Wieczorek <kuba.wieczorek@hashicorp.com>
This commit is contained in:
parent
bc2aa7e8ec
commit
b25410c747
@ -192,3 +192,15 @@ func ToSDKGroups(groups []*Group) []*logical.Group {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (g *Group) SCIMClientID() string {
|
||||
return g.ScimClientID
|
||||
}
|
||||
|
||||
func (e *Entity) SCIMClientID() string {
|
||||
return e.ScimClientID
|
||||
}
|
||||
|
||||
func (a *Alias) SCIMClientID() string {
|
||||
return a.ScimClientID
|
||||
}
|
||||
|
||||
@ -279,6 +279,9 @@ func (i *IdentityStore) handleAliasCreate(ctx context.Context, canonicalID, name
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := i.scimResourceCheck(ctx, &identity.Alias{ScimClientID: scimClientID}, "", true); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), logical.ErrPermissionDenied
|
||||
}
|
||||
var entity *identity.Entity
|
||||
if canonicalID != "" {
|
||||
entity, err = i.MemDBEntityByID(canonicalID, true)
|
||||
@ -370,6 +373,9 @@ func (i *IdentityStore) handleAliasUpdate(ctx context.Context, canonicalID, name
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := i.scimResourceCheck(ctx, alias, alias.ScimClientID, false); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), logical.ErrPermissionDenied
|
||||
}
|
||||
alias.LastUpdateTime = timestamppb.Now()
|
||||
|
||||
// Get our current entity, which may be the same as the new one if the
|
||||
@ -604,6 +610,11 @@ func (i *IdentityStore) pathAliasIDDelete() framework.OperationFunc {
|
||||
return logical.ErrorResponse("request and alias are in different namespaces"), logical.ErrPermissionDenied
|
||||
}
|
||||
|
||||
scimClientID := scimClientIDFromContext(ctx)
|
||||
if alias.ScimClientID != scimClientID {
|
||||
return logical.ErrorResponse("SCIM-managed resources must be modified through SCIM"), logical.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Fetch the associated entity
|
||||
entity, err := i.MemDBEntityByAliasIDInTxn(txn, alias.ID, true)
|
||||
if err != nil {
|
||||
|
||||
@ -597,7 +597,10 @@ func (i *IdentityStore) handleEntityDeleteCommon(ctx context.Context, txn *memdb
|
||||
if entity.NamespaceID != ns.ID {
|
||||
return nil
|
||||
}
|
||||
|
||||
scimClientID := scimClientIDFromContext(ctx)
|
||||
if entity.ScimClientID != scimClientID {
|
||||
return errors.New("SCIM-managed resources must be modified through SCIM")
|
||||
}
|
||||
// Remove entity ID as a member from all the groups it belongs, both
|
||||
// internal and external
|
||||
groups, err := i.MemDBGroupsByMemberEntityIDInTxn(txn, entity.ID, true, false)
|
||||
|
||||
@ -15,10 +15,11 @@ import (
|
||||
|
||||
// EntityBuilder is used to construct or update an identity.Entity.
|
||||
type EntityBuilder struct {
|
||||
store *IdentityStore
|
||||
entity *identity.Entity
|
||||
isNew bool
|
||||
err error
|
||||
store *IdentityStore
|
||||
entity *identity.Entity
|
||||
isNew bool
|
||||
originalSCIMID string
|
||||
err error
|
||||
}
|
||||
|
||||
// NewEntityBuilder creates a new builder instance.
|
||||
@ -57,6 +58,7 @@ func (b *EntityBuilder) WithID(id string) *EntityBuilder {
|
||||
}
|
||||
|
||||
b.entity = entity
|
||||
b.originalSCIMID = b.entity.ScimClientID
|
||||
b.isNew = false
|
||||
return b
|
||||
}
|
||||
@ -76,6 +78,7 @@ func (b *EntityBuilder) WithExternalID(ctx context.Context, externalID string) *
|
||||
if entityByExternalID != nil {
|
||||
// An entity with this external ID already exists, so we'll update it.
|
||||
b.entity = entityByExternalID
|
||||
b.originalSCIMID = b.entity.ScimClientID
|
||||
b.isNew = false
|
||||
} else {
|
||||
// No entity found, so we're just setting the external ID on the current one.
|
||||
@ -103,6 +106,7 @@ func (b *EntityBuilder) WithName(ctx context.Context, name string) *EntityBuilde
|
||||
case b.isNew:
|
||||
// We haven't loaded an entity yet, but one with this name exists. Let's update it.
|
||||
b.entity = entityByName
|
||||
b.originalSCIMID = b.entity.ScimClientID
|
||||
b.isNew = false
|
||||
case b.entity.ID == entityByName.ID:
|
||||
// The loaded entity and the one found by name are the same. No-op.
|
||||
@ -167,6 +171,10 @@ func (b *EntityBuilder) Build(ctx context.Context) (*logical.Response, error) {
|
||||
return logical.ErrorResponse(b.err.Error()), nil
|
||||
}
|
||||
|
||||
if err := b.store.scimResourceCheck(ctx, b.entity, b.originalSCIMID, b.isNew); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), logical.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Sanitize and persist the entity
|
||||
if err := b.store.sanitizeEntity(ctx, b.entity); err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -313,12 +313,15 @@ func (i *IdentityStore) handleGroupUpdateCommon(ctx context.Context, req *logica
|
||||
group.Name = groupName
|
||||
}
|
||||
|
||||
_, ok = d.Schema["scim_client_id"]
|
||||
originalSCIMID := group.ScimClientID
|
||||
scimClientID, ok := d.GetOk("scim_client_id")
|
||||
if ok {
|
||||
entitSCIMClientID := d.Get("scim_client_id").(string)
|
||||
group.ScimClientID = entitSCIMClientID
|
||||
group.ScimClientID = scimClientID.(string)
|
||||
}
|
||||
|
||||
if err := i.scimResourceCheck(ctx, group, originalSCIMID, newGroup); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
metadata, ok, err := d.GetOkErr("metadata")
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("failed to parse metadata: %v", err)), nil
|
||||
@ -525,6 +528,11 @@ func (i *IdentityStore) handleGroupDeleteCommon(ctx context.Context, key string,
|
||||
return logical.ErrorResponse("request namespace is not the same as the group namespace"), logical.ErrPermissionDenied
|
||||
}
|
||||
|
||||
scimID := scimClientIDFromContext(ctx)
|
||||
if scimID != group.ScimClientID {
|
||||
return logical.ErrorResponse("SCIM-managed resources must be modified through SCIM"), logical.ErrPermissionDenied
|
||||
}
|
||||
|
||||
// Delete group alias from memdb
|
||||
if group.Type == groupTypeExternal && group.Alias != nil {
|
||||
err = i.MemDBDeleteAliasByIDInTxn(txn, group.Alias.ID, true)
|
||||
|
||||
@ -3,7 +3,13 @@
|
||||
|
||||
package vault
|
||||
|
||||
import "github.com/hashicorp/go-memdb"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/hashicorp/go-memdb"
|
||||
"github.com/hashicorp/vault/helper/identity"
|
||||
)
|
||||
|
||||
// SCIM client storage prefix
|
||||
const scimClientStoragePrefix = "scim/client/"
|
||||
@ -69,3 +75,61 @@ func scimClientSchema(_ bool) *memdb.TableSchema {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type scimClientRequest struct{}
|
||||
|
||||
func addSCIMClientIDToContext(ctx context.Context, id string) context.Context {
|
||||
return context.WithValue(ctx, scimClientRequest{}, id)
|
||||
}
|
||||
|
||||
func scimClientIDFromContext(ctx context.Context) string {
|
||||
val := ctx.Value(scimClientRequest{})
|
||||
if val == nil {
|
||||
return ""
|
||||
}
|
||||
return val.(string)
|
||||
}
|
||||
|
||||
func (i *IdentityStore) scimResourceCheck(ctx context.Context, resource scimManaged, originalSCIMID string, isCreate bool) error {
|
||||
reqSCIMClientID := scimClientIDFromContext(ctx)
|
||||
resourceSCIMClientID := resource.SCIMClientID()
|
||||
|
||||
switch isCreate {
|
||||
case true:
|
||||
// The request must have come via a SCIM API in order to set
|
||||
// the SCIM client ID
|
||||
if reqSCIMClientID == "" && resourceSCIMClientID != "" {
|
||||
return errors.New("cannot set scim_client_id")
|
||||
}
|
||||
if reqSCIMClientID != "" && resourceSCIMClientID == "" {
|
||||
// this shouldn't ever happen
|
||||
return errors.New("cannot create a resource via SCIM without a SCIM client ID")
|
||||
}
|
||||
if reqSCIMClientID != resourceSCIMClientID {
|
||||
// this also shouldn't ever happen
|
||||
return errors.New("cannot create resource via SCIM with a different SCIM client ID")
|
||||
}
|
||||
|
||||
default:
|
||||
// if the resource is being updated, the SCIM client ID
|
||||
// cannot be modified
|
||||
if originalSCIMID != resourceSCIMClientID {
|
||||
return errors.New("cannot update scim_client_id")
|
||||
}
|
||||
// if the resource is being updated, this must be via SCIM
|
||||
if originalSCIMID != reqSCIMClientID {
|
||||
return errors.New("SCIM-managed resources must be modified through SCIM")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type scimManaged interface {
|
||||
SCIMClientID() string
|
||||
}
|
||||
|
||||
var (
|
||||
_ scimManaged = (*identity.Entity)(nil)
|
||||
_ scimManaged = (*identity.Group)(nil)
|
||||
_ scimManaged = (*identity.Alias)(nil)
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user