@@ -3,6 +3,7 @@ package scrapers
33import (
44 gocontext "context"
55 "encoding/json"
6+ "errors"
67 "fmt"
78 "math/rand/v2"
89 "os"
@@ -1628,3 +1629,131 @@ var _ = Describe("External groups with aliases e2e test", Ordered, func() {
16281629 Expect (developersID ).To (Equal (devsID )) // Same group, same ID
16291630 })
16301631})
1632+
1633+ var _ = Describe ("Config access logs upsert" , Ordered , func () {
1634+ fetchAccessLogRefs := func () (dutymodels.ConfigItem , uuid.UUID , api.ScrapeContext , uuid.UUID , func ()) {
1635+ var configItem dutymodels.ConfigItem
1636+ err := DefaultContext .DB ().Where ("scraper_id IS NOT NULL" ).First (& configItem ).Error
1637+ Expect (err ).NotTo (HaveOccurred (), "failed to find config item with scraper_id" )
1638+
1639+ scraperIDValue := lo .FromPtr (configItem .ScraperID )
1640+ Expect (scraperIDValue ).NotTo (BeEmpty ())
1641+
1642+ scraperID , err := uuid .Parse (scraperIDValue )
1643+ Expect (err ).NotTo (HaveOccurred (), "failed to parse scraper id" )
1644+
1645+ err = DefaultContext .DB ().First (& dutymodels.ConfigScraper {}, "id = ?" , scraperID ).Error
1646+ Expect (err ).NotTo (HaveOccurred (), "failed to find scraper" )
1647+
1648+ cleanupExternalUser := func () {}
1649+ var externalUser dutymodels.ExternalUser
1650+ err = DefaultContext .DB ().Where ("scraper_id = ?" , scraperID ).First (& externalUser ).Error
1651+ if err != nil {
1652+ if errors .Is (err , gorm .ErrRecordNotFound ) {
1653+ externalUser = dutymodels.ExternalUser {
1654+ ID : uuid .New (),
1655+ Name : "access-log-user" ,
1656+ AccountID : "test-account" ,
1657+ UserType : "user" ,
1658+ ScraperID : scraperID ,
1659+ CreatedAt : time .Now (),
1660+ }
1661+ err = DefaultContext .DB ().Create (& externalUser ).Error
1662+ Expect (err ).NotTo (HaveOccurred (), "failed to create external user" )
1663+ cleanupExternalUser = func () {
1664+ err := DefaultContext .DB ().Delete (& externalUser ).Error
1665+ Expect (err ).NotTo (HaveOccurred (), "failed to delete external user" )
1666+ }
1667+ } else {
1668+ Expect (err ).NotTo (HaveOccurred (), "failed to fetch external user" )
1669+ }
1670+ }
1671+
1672+ return configItem , scraperID , api .NewScrapeContext (DefaultContext ), externalUser .ID , cleanupExternalUser
1673+ }
1674+
1675+ cleanupAccessLog := func (configID , externalUserID , scraperID uuid.UUID ) {
1676+ err := DefaultContext .DB ().Where ("config_id = ? AND external_user_id = ? AND scraper_id = ?" ,
1677+ configID , externalUserID , scraperID ).Delete (& dutymodels.ConfigAccessLog {}).Error
1678+ Expect (err ).NotTo (HaveOccurred (), "failed to delete access logs" )
1679+ }
1680+
1681+ It ("should update access log when newer" , func () {
1682+ configItem , scraperID , scraperCtx , externalUserID , cleanupExternalUser := fetchAccessLogRefs ()
1683+ DeferCleanup (func () {
1684+ cleanupAccessLog (configItem .ID , externalUserID , scraperID )
1685+ cleanupExternalUser ()
1686+ })
1687+
1688+ olderTime := time .Now ().Add (- 2 * time .Hour )
1689+ latestAccessTime := time .Now ().Add (- 1 * time .Hour )
1690+
1691+ olderLog := dutymodels.ConfigAccessLog {
1692+ ConfigID : configItem .ID ,
1693+ ExternalUserID : externalUserID ,
1694+ ScraperID : scraperID ,
1695+ CreatedAt : olderTime ,
1696+ MFA : false ,
1697+ }
1698+
1699+ err := db .SaveConfigAccessLog (scraperCtx , & olderLog )
1700+ Expect (err ).NotTo (HaveOccurred ())
1701+
1702+ newerLog := dutymodels.ConfigAccessLog {
1703+ ConfigID : configItem .ID ,
1704+ ExternalUserID : externalUserID ,
1705+ ScraperID : scraperID ,
1706+ CreatedAt : latestAccessTime ,
1707+ MFA : true ,
1708+ }
1709+
1710+ err = db .SaveConfigAccessLog (scraperCtx , & newerLog )
1711+ Expect (err ).NotTo (HaveOccurred ())
1712+
1713+ var storedLog dutymodels.ConfigAccessLog
1714+ err = DefaultContext .DB ().Where ("config_id = ? AND external_user_id = ? AND scraper_id = ?" ,
1715+ configItem .ID , externalUserID , scraperID ).First (& storedLog ).Error
1716+ Expect (err ).NotTo (HaveOccurred ())
1717+ Expect (storedLog .MFA ).To (BeTrue ())
1718+ Expect (storedLog .CreatedAt ).To (BeTemporally ("~" , latestAccessTime , time .Second ))
1719+ })
1720+
1721+ It ("should ignore older access log" , func () {
1722+ configItem , scraperID , scraperCtx , externalUserID , cleanupExternalUser := fetchAccessLogRefs ()
1723+ DeferCleanup (func () {
1724+ cleanupAccessLog (configItem .ID , externalUserID , scraperID )
1725+ cleanupExternalUser ()
1726+ })
1727+
1728+ latestAccessTime := time .Now ().Add (- 1 * time .Hour )
1729+ latestLog := dutymodels.ConfigAccessLog {
1730+ ConfigID : configItem .ID ,
1731+ ExternalUserID : externalUserID ,
1732+ ScraperID : scraperID ,
1733+ CreatedAt : latestAccessTime ,
1734+ MFA : true ,
1735+ }
1736+
1737+ err := db .SaveConfigAccessLog (scraperCtx , & latestLog )
1738+ Expect (err ).NotTo (HaveOccurred ())
1739+
1740+ olderTime := time .Now ().Add (- 3 * time .Hour )
1741+ olderLog := dutymodels.ConfigAccessLog {
1742+ ConfigID : configItem .ID ,
1743+ ExternalUserID : externalUserID ,
1744+ ScraperID : scraperID ,
1745+ CreatedAt : olderTime ,
1746+ MFA : false ,
1747+ }
1748+
1749+ err = db .SaveConfigAccessLog (scraperCtx , & olderLog )
1750+ Expect (err ).NotTo (HaveOccurred ())
1751+
1752+ var storedLog dutymodels.ConfigAccessLog
1753+ err = DefaultContext .DB ().Where ("config_id = ? AND external_user_id = ? AND scraper_id = ?" ,
1754+ configItem .ID , externalUserID , scraperID ).First (& storedLog ).Error
1755+ Expect (err ).NotTo (HaveOccurred ())
1756+ Expect (storedLog .MFA ).To (BeTrue ())
1757+ Expect (storedLog .CreatedAt ).To (BeTemporally ("~" , latestAccessTime , time .Second ))
1758+ })
1759+ })
0 commit comments