2023-03-15 19:00:52 +03:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
2024-01-02 21:36:20 +03:00
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
2023-03-15 19:00:52 +03:00
|
|
|
|
2022-02-18 00:08:51 +03:00
|
|
|
package vault
|
|
|
|
|
|
|
|
import (
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/vault/sdk/queue"
|
|
|
|
)
|
|
|
|
|
|
|
|
// NewLoginMFAPriorityQueue initializes the internal data structures and returns a new
|
|
|
|
// PriorityQueue
|
|
|
|
func NewLoginMFAPriorityQueue() *LoginMFAPriorityQueue {
|
|
|
|
pq := queue.New()
|
|
|
|
loginPQ := &LoginMFAPriorityQueue{
|
|
|
|
wrapped: pq,
|
|
|
|
}
|
|
|
|
return loginPQ
|
|
|
|
}
|
|
|
|
|
|
|
|
type LoginMFAPriorityQueue struct {
|
|
|
|
wrapped *queue.PriorityQueue
|
|
|
|
|
|
|
|
// Here is a scenarios in which the lock is needed. For example, suppose
|
|
|
|
// RemoveExpiredMfaAuthResponse function pops an item to check if the item
|
|
|
|
// has been expired or not and assume that the item is still valid. Then,
|
|
|
|
// if in the meantime, an MFA validation request comes in for the same
|
|
|
|
// item, the /sys/mfa/validate endpoint will return invalid request ID
|
|
|
|
// which is not true.
|
|
|
|
l sync.RWMutex
|
|
|
|
}
|
|
|
|
|
|
|
|
// Len returns the count of items in the Priority Queue
|
|
|
|
func (pq *LoginMFAPriorityQueue) Len() int {
|
|
|
|
pq.l.Lock()
|
|
|
|
defer pq.l.Unlock()
|
|
|
|
return pq.wrapped.Len()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Push pushes an item on to the queue. This is a wrapper/convenience
|
|
|
|
// method that calls heap.Push, so consumers do not need to invoke heap
|
|
|
|
// functions directly. Items must have unique Keys, and Items in the queue
|
|
|
|
// cannot be updated. To modify an Item, users must first remove it and re-push
|
|
|
|
// it after modifications
|
|
|
|
func (pq *LoginMFAPriorityQueue) Push(resp *MFACachedAuthResponse) error {
|
|
|
|
pq.l.Lock()
|
|
|
|
defer pq.l.Unlock()
|
|
|
|
|
|
|
|
item := &queue.Item{
|
|
|
|
Key: resp.RequestID,
|
|
|
|
Value: resp,
|
|
|
|
Priority: resp.TimeOfStorage.Unix(),
|
|
|
|
}
|
|
|
|
|
|
|
|
return pq.wrapped.Push(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
// PopByKey searches the queue for an item with the given key and removes it
|
|
|
|
// from the queue if found. Returns nil if not found.
|
|
|
|
func (pq *LoginMFAPriorityQueue) PopByKey(reqID string) (*MFACachedAuthResponse, error) {
|
|
|
|
pq.l.Lock()
|
|
|
|
defer pq.l.Unlock()
|
|
|
|
|
|
|
|
item, err := pq.wrapped.PopByKey(reqID)
|
|
|
|
if err != nil || item == nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return item.Value.(*MFACachedAuthResponse), 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
|
|
|
|
// left to be removed, true otherwise.
|
|
|
|
// cutoffTime should normally be time.Now() except for tests.
|
|
|
|
func (pq *LoginMFAPriorityQueue) RemoveExpiredMfaAuthResponse(expiryTime time.Duration, cutoffTime time.Time) error {
|
|
|
|
pq.l.Lock()
|
|
|
|
defer pq.l.Unlock()
|
|
|
|
|
|
|
|
item, err := pq.wrapped.Pop()
|
|
|
|
if err != nil && err != queue.ErrEmpty {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err == queue.ErrEmpty {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
mfaResp := item.Value.(*MFACachedAuthResponse)
|
|
|
|
|
|
|
|
storageTime := mfaResp.TimeOfStorage
|
|
|
|
if cutoffTime.Before(storageTime.Add(expiryTime)) {
|
|
|
|
// the highest priority entry has not been expired yet, pushing it
|
|
|
|
// back and return
|
|
|
|
err := pq.wrapped.Push(item)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|