mirror of
https://github.com/hashicorp/vault.git
synced 2025-09-18 20:31:08 +02:00
Co-authored-by: Kuba Wieczorek <kuba.wieczorek@hashicorp.com>
This commit is contained in:
parent
eaf949cb1f
commit
5d632efcf3
@ -153,6 +153,19 @@ func (pq *PriorityQueue) PopByKey(key string) (*Item, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// PeekByKey returns the item with the given key without removing it from the queue.
|
||||
func (pq *PriorityQueue) PeekByKey(id string) *Item {
|
||||
pq.lock.RLock()
|
||||
defer pq.lock.RUnlock()
|
||||
|
||||
item, ok := pq.dataMap[id]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
// Len returns the number of items in the queue data structure. Do not use this
|
||||
// method directly on the queue, use PriorityQueue.Len() instead.
|
||||
func (q queue) Len() int { return len(q) }
|
||||
|
@ -141,6 +141,67 @@ func TestPriorityQueue_Pop(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPriorityQueue_PeekByKey tests the PeekByKey method of PriorityQueue.
|
||||
// It verifies that PeekByKey returns the correct item without removing it from the queue,
|
||||
// handles non-existing keys appropriately, works correctly on empty queues, and
|
||||
// properly handles empty key strings.
|
||||
func TestPriorityQueue_PeekByKey(t *testing.T) {
|
||||
pq := New()
|
||||
tc := testCases()
|
||||
expectedLength := len(tc)
|
||||
|
||||
// Peek from empty queue
|
||||
peekedItem := pq.PeekByKey("item-2")
|
||||
if peekedItem != nil {
|
||||
t.Fatal("expected nil when peeking from empty queue, got item")
|
||||
}
|
||||
if pq.Len() != 0 {
|
||||
t.Fatalf("expected empty queue to remain size 0, got %d", pq.Len())
|
||||
}
|
||||
|
||||
// Push test items
|
||||
for _, item := range tc {
|
||||
if err := pq.Push(item); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Peek with empty key
|
||||
peekedItem = pq.PeekByKey("")
|
||||
if peekedItem != nil {
|
||||
t.Fatal("expected nil for empty key, got item")
|
||||
}
|
||||
// Verify queue size unchanged
|
||||
if pq.Len() != expectedLength {
|
||||
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||
}
|
||||
|
||||
// Peek at non-existing item
|
||||
peekedItem = pq.PeekByKey("non-existing-key")
|
||||
if peekedItem != nil {
|
||||
t.Fatal("expected nil for non-existing key, got item")
|
||||
}
|
||||
// Verify queue size unchanged
|
||||
if pq.Len() != expectedLength {
|
||||
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||
}
|
||||
|
||||
// Peek at a specific item
|
||||
peekedItem = pq.PeekByKey("item-2")
|
||||
if peekedItem == nil {
|
||||
t.Fatal("expected to peek item-2, got nil")
|
||||
}
|
||||
// Verify queue size unchanged
|
||||
if pq.Len() != expectedLength {
|
||||
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||
}
|
||||
// Verify item still exists in queue
|
||||
stillExists := pq.PeekByKey("item-2")
|
||||
if stillExists == nil {
|
||||
t.Fatal("item should still exist after peek")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPriorityQueue_PopByKey(t *testing.T) {
|
||||
pq := New()
|
||||
|
||||
|
@ -3993,14 +3993,34 @@ func (c *Core) loadLoginMFAConfigs(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MFACachedAuthResponse represents an authentication response that has been
|
||||
// temporarily cached during a two-phase MFA (Multi-Factor Authentication) login flow.
|
||||
//
|
||||
// This struct is used when an MFA enforcement is configured and a login request
|
||||
// lacks MFA credentials. Instead of completing the authentication immediately,
|
||||
// Vault caches the auth response and returns an MFARequirement to the client.
|
||||
// The client must then complete MFA validation using the mfa/validate endpoint
|
||||
// to retrieve the cached authentication and receive their token.
|
||||
//
|
||||
// The cached response includes the original authentication details along with
|
||||
// request metadata needed for MFA validation, such as the client's IP address
|
||||
// for methods like Duo that require connection information.
|
||||
//
|
||||
// This struct is also used to cache self-enrollment TOTP MFA secrets generated
|
||||
// during login when self-enrollment is enabled. This allows Vault to avoid
|
||||
// persisting the newly generated MFA secret until it has been successfully used
|
||||
// for validating an MFA-enforced login request.
|
||||
type MFACachedAuthResponse struct {
|
||||
CachedAuth *logical.Auth
|
||||
RequestPath string
|
||||
RequestNSID string
|
||||
RequestNSPath string
|
||||
RequestConnRemoteAddr string
|
||||
TimeOfStorage time.Time
|
||||
RequestID string
|
||||
CachedAuth *logical.Auth
|
||||
RequestPath string
|
||||
RequestNSID string
|
||||
RequestNSPath string
|
||||
RequestConnRemoteAddr string
|
||||
TimeOfStorage time.Time
|
||||
RequestID string
|
||||
SelfEnrollmentMFASecret *mfa.Secret
|
||||
// Store the secret key string separately to avoid anyone accidentally persisting it on an Entity.
|
||||
SelfEnrollmentMFASecretKey string
|
||||
}
|
||||
|
||||
func (c *Core) setupCachedMFAResponseAuth() {
|
||||
|
@ -165,6 +165,7 @@ func (i *IdentityStore) paths() []*framework.Path {
|
||||
mfaDuoPaths(i),
|
||||
mfaPingIDPaths(i),
|
||||
mfaLoginEnforcementPaths(i),
|
||||
mfaLoginEnterprisePaths(i),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -71,6 +72,20 @@ func (pq *LoginMFAPriorityQueue) PopByKey(reqID string) (*MFACachedAuthResponse,
|
||||
return item.Value.(*MFACachedAuthResponse), nil
|
||||
}
|
||||
|
||||
// PeekByKey returns the item with the given key without removing it from the queue.
|
||||
func (pq *LoginMFAPriorityQueue) PeekByKey(reqID string) (*MFACachedAuthResponse, error) {
|
||||
pq.l.RLock()
|
||||
defer pq.l.RUnlock()
|
||||
|
||||
item := pq.wrapped.PeekByKey(reqID)
|
||||
if item == nil {
|
||||
return nil, errors.New("no item found with the given request ID")
|
||||
}
|
||||
|
||||
mfaResp := item.Value.(*MFACachedAuthResponse)
|
||||
return mfaResp, nil
|
||||
}
|
||||
|
||||
// RemoveExpiredMfaAuthResponse pops elements of the queue and check
|
||||
// if the entry has expired or not. If the entry has not expired, it pushes
|
||||
// back the entry to the queue. It returns false if there is no expired element
|
||||
|
@ -87,6 +87,89 @@ func TestLoginMFAPriorityQueue_PushPopByKey(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLoginMFAPriorityQueue_PeekByKey tests the PeekByKey method of
|
||||
// LoginMFAPriorityQueue. It verifies that PeekByKey returns the correct item
|
||||
// without removing it from the queue, returns errors on unhappy paths, handles
|
||||
// non-existing keys appropriately, works correctly on empty queues, and
|
||||
// properly handles empty key strings.
|
||||
func TestLoginMFAPriorityQueue_PeekByKey(t *testing.T) {
|
||||
pq := NewLoginMFAPriorityQueue()
|
||||
tc := testCases()
|
||||
expectedLength := len(tc)
|
||||
|
||||
// Peek from empty queue
|
||||
peekedItem, err := pq.PeekByKey("item-2")
|
||||
if peekedItem != nil {
|
||||
t.Fatal("expected nil when peeking from empty queue, got item")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("expected an error when peeking from empty queue, got nil")
|
||||
}
|
||||
if pq.Len() != 0 {
|
||||
t.Fatalf("expected empty queue to remain size 0, got %d", pq.Len())
|
||||
}
|
||||
|
||||
// Push test items
|
||||
for _, item := range tc {
|
||||
if err := pq.Push(item); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Peek with empty key
|
||||
peekedItem, err = pq.PeekByKey("")
|
||||
if peekedItem != nil {
|
||||
t.Fatal("expected nil for empty key, got item")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("expected error when peeking with empty key , got nil")
|
||||
}
|
||||
// Verify queue size unchanged
|
||||
if pq.Len() != expectedLength {
|
||||
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||
}
|
||||
|
||||
// Peek at non-existing item
|
||||
peekedItem, err = pq.PeekByKey("non-existing-key")
|
||||
if peekedItem != nil {
|
||||
t.Fatal("expected nil for non-existing key, got item")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatal("expected error when peeking with non-existing key, got nil")
|
||||
}
|
||||
// Verify queue size unchanged
|
||||
if pq.Len() != expectedLength {
|
||||
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||
}
|
||||
|
||||
// Peek at a specific item
|
||||
peekedItem, err = pq.PeekByKey(tc[2].RequestID)
|
||||
if peekedItem == nil {
|
||||
t.Fatal("expected to peek item-2, got nil")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("expected no error when peeking existing key, got", err)
|
||||
}
|
||||
if peekedItem.RequestID != tc[2].RequestID {
|
||||
t.Fatal("expected the same item on subsequent peeks, got different items")
|
||||
}
|
||||
// Verify queue size unchanged
|
||||
if pq.Len() != expectedLength {
|
||||
t.Fatalf("expected queue size to remain %d, got %d", expectedLength, pq.Len())
|
||||
}
|
||||
// Verify item still exists in queue
|
||||
stillExists, err := pq.PeekByKey(tc[2].RequestID)
|
||||
if stillExists == nil {
|
||||
t.Fatal("item should still exist after peek")
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal("expected no error when peeking existing key for the second time, got", err)
|
||||
}
|
||||
if stillExists.RequestID != tc[2].RequestID {
|
||||
t.Fatal("expected the same item on subsequent peeks, got different items")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginMFARemoveStaleEntries(t *testing.T) {
|
||||
pq := NewLoginMFAPriorityQueue()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user