Quellcode durchsuchen

Support password-fallbackPassword array in requireBasicAuth (#27237)

GitOrigin-RevId: 33b15a05996bfa0190041f347772867a9667e2ca
Domagoj Kriskovic vor 1 Jahr
Ursprung
Commit
868d562d96

+ 16 - 1
services/web/app/src/Features/Authentication/AuthenticationController.js

@@ -36,7 +36,22 @@ function send401WithChallenge(res) {
 function checkCredentials(userDetailsMap, user, password) {
   const expectedPassword = userDetailsMap.get(user)
   const userExists = userDetailsMap.has(user) && expectedPassword // user exists with a non-null password
-  const isValid = userExists && tsscmp(expectedPassword, password)
+
+  let isValid = false
+  if (userExists) {
+    if (Array.isArray(expectedPassword)) {
+      const isValidPrimary = Boolean(
+        expectedPassword[0] && tsscmp(expectedPassword[0], password)
+      )
+      const isValidFallback = Boolean(
+        expectedPassword[1] && tsscmp(expectedPassword[1], password)
+      )
+      isValid = isValidPrimary || isValidFallback
+    } else {
+      isValid = tsscmp(expectedPassword, password)
+    }
+  }
+
   if (!isValid) {
     logger.err({ user }, 'invalid login details')
   }

+ 327 - 0
services/web/test/unit/src/Authentication/AuthenticationControllerTests.js

@@ -1500,4 +1500,331 @@ describe('AuthenticationController', function () {
       })
     })
   })
+
+  describe('checkCredentials', function () {
+    beforeEach(function () {
+      this.userDetailsMap = new Map()
+      this.logger.err = sinon.stub()
+      this.Metrics.inc = sinon.stub()
+    })
+
+    describe('with valid credentials', function () {
+      describe('single password', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', 'correctpassword')
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'correctpassword'
+          )
+        })
+
+        it('should return true', function () {
+          this.result.should.equal(true)
+        })
+
+        it('should not log an error', function () {
+          this.logger.err.called.should.equal(false)
+        })
+
+        it('should record success metrics', function () {
+          this.Metrics.inc.should.have.been.calledWith(
+            'security.http-auth.check-credentials',
+            1,
+            {
+              path: 'known-user',
+              status: 'pass',
+            }
+          )
+        })
+      })
+
+      describe('array with primary password', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', ['primary', 'fallback'])
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'primary'
+          )
+        })
+
+        it('should return true', function () {
+          this.result.should.equal(true)
+        })
+
+        it('should not log an error', function () {
+          this.logger.err.called.should.equal(false)
+        })
+
+        it('should record success metrics', function () {
+          this.Metrics.inc.should.have.been.calledWith(
+            'security.http-auth.check-credentials',
+            1,
+            {
+              path: 'known-user',
+              status: 'pass',
+            }
+          )
+        })
+      })
+
+      describe('array with fallback password', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', ['primary', 'fallback'])
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'fallback'
+          )
+        })
+
+        it('should return true', function () {
+          this.result.should.equal(true)
+        })
+
+        it('should not log an error', function () {
+          this.logger.err.called.should.equal(false)
+        })
+
+        it('should record success metrics', function () {
+          this.Metrics.inc.should.have.been.calledWith(
+            'security.http-auth.check-credentials',
+            1,
+            {
+              path: 'known-user',
+              status: 'pass',
+            }
+          )
+        })
+      })
+    })
+
+    describe('with invalid credentials', function () {
+      describe('unknown user', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', 'correctpassword')
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'unknownuser',
+            'anypassword'
+          )
+        })
+
+        it('should return false', function () {
+          this.result.should.equal(false)
+        })
+
+        it('should log an error', function () {
+          this.logger.err.should.have.been.calledWith(
+            { user: 'unknownuser' },
+            'invalid login details'
+          )
+        })
+
+        it('should record failure metrics', function () {
+          this.Metrics.inc.should.have.been.calledWith(
+            'security.http-auth.check-credentials',
+            1,
+            {
+              path: 'unknown-user',
+              status: 'fail',
+            }
+          )
+        })
+      })
+
+      describe('wrong password', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', 'correctpassword')
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'wrongpassword'
+          )
+        })
+
+        it('should return false', function () {
+          this.result.should.equal(false)
+        })
+
+        it('should log an error', function () {
+          this.logger.err.should.have.been.calledWith(
+            { user: 'testuser' },
+            'invalid login details'
+          )
+        })
+
+        it('should record failure metrics', function () {
+          this.Metrics.inc.should.have.been.calledWith(
+            'security.http-auth.check-credentials',
+            1,
+            {
+              path: 'known-user',
+              status: 'fail',
+            }
+          )
+        })
+      })
+
+      describe('wrong password with array', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', ['primary', 'fallback'])
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'wrongpassword'
+          )
+        })
+
+        it('should return false', function () {
+          this.result.should.equal(false)
+        })
+
+        it('should log an error', function () {
+          this.logger.err.should.have.been.calledWith(
+            { user: 'testuser' },
+            'invalid login details'
+          )
+        })
+
+        it('should record failure metrics', function () {
+          this.Metrics.inc.should.have.been.calledWith(
+            'security.http-auth.check-credentials',
+            1,
+            {
+              path: 'known-user',
+              status: 'fail',
+            }
+          )
+        })
+      })
+
+      describe('null user entry', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', null)
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'anypassword'
+          )
+        })
+
+        it('should return false', function () {
+          this.result.should.equal(false)
+        })
+
+        it('should log an error', function () {
+          this.logger.err.should.have.been.calledWith(
+            { user: 'testuser' },
+            'invalid login details'
+          )
+        })
+
+        it('should record failure metrics for unknown user', function () {
+          this.Metrics.inc.should.have.been.calledWith(
+            'security.http-auth.check-credentials',
+            1,
+            {
+              path: 'unknown-user',
+              status: 'fail',
+            }
+          )
+        })
+      })
+
+      describe('empty primary password in array', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', ['', 'fallback'])
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'fallback'
+          )
+        })
+
+        it('should return true with fallback password', function () {
+          this.result.should.equal(true)
+        })
+
+        it('should not log an error', function () {
+          this.logger.err.called.should.equal(false)
+        })
+      })
+
+      describe('empty fallback password in array', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', ['primary', ''])
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'primary'
+          )
+        })
+
+        it('should return true with primary password', function () {
+          this.result.should.equal(true)
+        })
+
+        it('should not log an error', function () {
+          this.logger.err.called.should.equal(false)
+        })
+      })
+
+      describe('both passwords empty in array', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', ['', ''])
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'anypassword'
+          )
+        })
+
+        it('should return false', function () {
+          this.result.should.equal(false)
+        })
+
+        it('should log an error', function () {
+          this.logger.err.should.have.been.calledWith(
+            { user: 'testuser' },
+            'invalid login details'
+          )
+        })
+      })
+
+      describe('empty single password', function () {
+        beforeEach(function () {
+          this.userDetailsMap.set('testuser', '')
+          this.result = this.AuthenticationController.checkCredentials(
+            this.userDetailsMap,
+            'testuser',
+            'anypassword'
+          )
+        })
+
+        it('should return false', function () {
+          this.result.should.equal(false)
+        })
+
+        it('should log an error', function () {
+          this.logger.err.should.have.been.calledWith(
+            { user: 'testuser' },
+            'invalid login details'
+          )
+        })
+
+        it('should record failure metrics for unknown user', function () {
+          this.Metrics.inc.should.have.been.calledWith(
+            'security.http-auth.check-credentials',
+            1,
+            {
+              path: 'unknown-user',
+              status: 'fail',
+            }
+          )
+        })
+      })
+    })
+  })
 })