2018-07-27 10:18:29 -04:00
|
|
|
/*
|
|
|
|
* Copyright 2018 Comcast Cable Communications Management, LLC
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package controllers
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
import cats.effect.IO
|
2018-07-27 10:18:29 -04:00
|
|
|
import javax.naming.NamingEnumeration
|
|
|
|
import javax.naming.directory._
|
|
|
|
import controllers.LdapAuthenticator.{ContextCreator, LdapByDomainAuthenticator}
|
|
|
|
import org.specs2.mock.Mockito
|
|
|
|
import org.specs2.mock.mockito.ArgumentCapture
|
|
|
|
import org.specs2.mutable.Specification
|
2018-09-18 11:51:31 -04:00
|
|
|
import play.api.{Configuration, Environment}
|
2019-06-11 12:15:52 -04:00
|
|
|
import vinyldns.core.health.HealthCheck._
|
2019-07-01 19:30:45 -04:00
|
|
|
import vinyldns.core.domain.membership.User
|
|
|
|
import vinyldns.core.health.HealthCheck.HealthCheckError
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
class LdapAuthenticatorSpec extends Specification with Mockito {
|
|
|
|
|
|
|
|
case class Mocks(
|
|
|
|
contextCreator: ContextCreator,
|
|
|
|
context: DirContext,
|
|
|
|
searchResults: NamingEnumeration[SearchResult],
|
|
|
|
searchNext: SearchResult,
|
|
|
|
byDomainAuthenticator: LdapByDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
attributes: Attributes
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* creates a container holding all mocks necessary to create
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
def createMocks: Mocks = {
|
|
|
|
val contextCreator: ContextCreator = mock[ContextCreator]
|
|
|
|
val context = mock[DirContext]
|
2019-06-11 12:15:52 -04:00
|
|
|
contextCreator.apply(anyString, anyString).returns(Right(context))
|
2018-07-27 10:18:29 -04:00
|
|
|
val searchResults = mock[NamingEnumeration[SearchResult]]
|
|
|
|
searchResults.hasMore.returns(true)
|
|
|
|
val mockAttribute = mock[Attribute]
|
|
|
|
mockAttribute.get().returns("")
|
|
|
|
val mockAttributes = mock[Attributes]
|
|
|
|
mockAttributes.get(anyString).returns(mockAttribute)
|
|
|
|
val searchNext = mock[SearchResult]
|
|
|
|
searchResults.next.returns(searchNext)
|
|
|
|
searchNext.getNameInNamespace.returns("")
|
|
|
|
searchNext.getAttributes.returns(mockAttributes)
|
|
|
|
context.search(anyString, anyString, any[SearchControls]).returns(searchResults)
|
|
|
|
val byDomainAuthenticator = new LdapByDomainAuthenticator(Settings, contextCreator)
|
|
|
|
|
|
|
|
Mocks(contextCreator, context, searchResults, searchNext, byDomainAuthenticator, mockAttributes)
|
|
|
|
}
|
|
|
|
|
|
|
|
val testDomain1 = LdapSearchDomain("someDomain", "DC=test,DC=test,DC=com")
|
|
|
|
val testDomain2 = LdapSearchDomain("anotherDomain", "DC=test,DC=com")
|
2019-07-01 19:30:45 -04:00
|
|
|
val nonexistentUser = User("does-not-exist", "accessKey", "secretKey")
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
"LdapAuthenticator" should {
|
2018-09-18 11:51:31 -04:00
|
|
|
"apply method must create an LDAP Authenticator" in {
|
|
|
|
val testConfig: Configuration =
|
|
|
|
Configuration.load(Environment.simple()) ++ Configuration.from(
|
2019-11-11 13:11:41 -05:00
|
|
|
Map("portal.test_login" -> false)
|
|
|
|
)
|
2018-09-18 11:51:31 -04:00
|
|
|
val underTest = LdapAuthenticator.apply(new Settings(testConfig))
|
|
|
|
underTest must beAnInstanceOf[LdapAuthenticator]
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
2018-09-18 11:51:31 -04:00
|
|
|
"apply method must create a Test Authenticator if selected" in {
|
|
|
|
val testConfig: Configuration =
|
|
|
|
Configuration.load(Environment.simple()) ++ Configuration.from(
|
2019-11-11 13:11:41 -05:00
|
|
|
Map("portal.test_login" -> true)
|
|
|
|
)
|
2018-09-18 11:51:31 -04:00
|
|
|
val underTest = LdapAuthenticator.apply(new Settings(testConfig))
|
2018-07-27 10:18:29 -04:00
|
|
|
underTest must beAnInstanceOf[TestAuthenticator]
|
|
|
|
}
|
|
|
|
".authenticate" should {
|
2018-12-10 09:57:24 -05:00
|
|
|
"authenticate first with 1st domain" in {
|
2018-07-27 10:18:29 -04:00
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.authenticate(testDomain1, "foo", "bar")
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Right(LdapUserDetails("", "", None, None, None)))
|
2018-07-27 10:18:29 -04:00
|
|
|
val authenticator =
|
|
|
|
new LdapAuthenticator(List(testDomain1), byDomainAuthenticator, mock[ServiceAccount])
|
|
|
|
val response = authenticator.authenticate("foo", "bar")
|
|
|
|
|
|
|
|
there.was(one(byDomainAuthenticator).authenticate(testDomain1, "foo", "bar"))
|
|
|
|
there.was(no(byDomainAuthenticator).authenticate(testDomain2, "foo", "bar"))
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
"and return Right if authenticated" in {
|
|
|
|
response must beRight
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
"authenticate with 2nd domain if 1st fails with UserDoesNotExistException" in {
|
2018-07-27 10:18:29 -04:00
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.authenticate(testDomain1, "foo", "bar")
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(UserDoesNotExistException("first failed")))
|
2018-07-27 10:18:29 -04:00
|
|
|
byDomainAuthenticator
|
|
|
|
.authenticate(testDomain2, "foo", "bar")
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Right(LdapUserDetails("", "", None, None, None)))
|
2018-07-27 10:18:29 -04:00
|
|
|
val authenticator = new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
mock[ServiceAccount]
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val response = authenticator.authenticate("foo", "bar")
|
|
|
|
|
|
|
|
there.was(one(byDomainAuthenticator).authenticate(testDomain1, "foo", "bar"))
|
|
|
|
there.was(one(byDomainAuthenticator).authenticate(testDomain2, "foo", "bar"))
|
|
|
|
|
|
|
|
"and return a Success if authenticated" in {
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beRight
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
"return an error if all domains lookups fail but services are up" in {
|
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.authenticate(testDomain1, "foo", "bar")
|
|
|
|
.returns(Left(UserDoesNotExistException("first failed")))
|
|
|
|
byDomainAuthenticator
|
|
|
|
.authenticate(testDomain2, "foo", "bar")
|
|
|
|
.returns(Left(UserDoesNotExistException("second failed")))
|
|
|
|
val authenticator = new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
mock[ServiceAccount]
|
|
|
|
)
|
2019-06-11 12:15:52 -04:00
|
|
|
|
|
|
|
val response = authenticator.authenticate("foo", "bar")
|
|
|
|
|
|
|
|
there.was(one(byDomainAuthenticator).authenticate(testDomain1, "foo", "bar"))
|
|
|
|
there.was(one(byDomainAuthenticator).authenticate(testDomain2, "foo", "bar"))
|
|
|
|
|
|
|
|
"and return error message" in {
|
|
|
|
response must beLeft
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
"return an error if all domains fail with at least one LDAP connectivity issue" in {
|
2018-07-27 10:18:29 -04:00
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.authenticate(testDomain1, "foo", "bar")
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(UserDoesNotExistException("first failed")))
|
2018-07-27 10:18:29 -04:00
|
|
|
byDomainAuthenticator
|
|
|
|
.authenticate(testDomain2, "foo", "bar")
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(LdapServiceException("some bad exception")))
|
2018-07-27 10:18:29 -04:00
|
|
|
val authenticator = new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
mock[ServiceAccount]
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val response = authenticator.authenticate("foo", "bar")
|
|
|
|
|
|
|
|
there.was(one(byDomainAuthenticator).authenticate(testDomain1, "foo", "bar"))
|
|
|
|
there.was(one(byDomainAuthenticator).authenticate(testDomain2, "foo", "bar"))
|
|
|
|
|
|
|
|
"and return error message" in {
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beLeft
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
}
|
2019-07-01 19:30:45 -04:00
|
|
|
|
|
|
|
"return an error if no LDAP search domains are provided" in {
|
|
|
|
val noDomainsLdapAuthenticator =
|
|
|
|
new LdapAuthenticator(List(), mock[LdapByDomainAuthenticator], mock[ServiceAccount])
|
|
|
|
|
|
|
|
noDomainsLdapAuthenticator.authenticate("someUserName", "somePassword") must beLeft
|
|
|
|
}
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
".lookup" should {
|
2018-12-10 09:57:24 -05:00
|
|
|
"lookup first with 1st domain" in {
|
2018-07-27 10:18:29 -04:00
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
2018-12-10 09:57:24 -05:00
|
|
|
val serviceAccount = ServiceAccount("first", "foo", "bar")
|
2018-07-27 10:18:29 -04:00
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "foo", serviceAccount)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Right(LdapUserDetails("", "", None, None, None)))
|
2018-07-27 10:18:29 -04:00
|
|
|
val authenticator =
|
|
|
|
new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
serviceAccount
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val response = authenticator.lookup("foo")
|
|
|
|
|
|
|
|
there.was(one(byDomainAuthenticator).lookup(testDomain1, "foo", serviceAccount))
|
|
|
|
there.was(no(byDomainAuthenticator).authenticate(testDomain2, "foo", "bar"))
|
|
|
|
|
|
|
|
"and return details if authenticated" in {
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beRight
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
2019-07-01 19:30:45 -04:00
|
|
|
|
|
|
|
"return an error if no LDAP search domains are provided" in {
|
|
|
|
val noDomainsLdapAuthenticator =
|
|
|
|
new LdapAuthenticator(List(), mock[LdapByDomainAuthenticator], mock[ServiceAccount])
|
|
|
|
|
|
|
|
noDomainsLdapAuthenticator.lookup("someUserName") must beLeft
|
|
|
|
}
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
"lookup with 2nd domain if 1st fails with UserDoesNotExistException" in {
|
2018-07-27 10:18:29 -04:00
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
val serviceAccount = mock[ServiceAccount]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "foo", serviceAccount)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(UserDoesNotExistException("first failed")))
|
2018-07-27 10:18:29 -04:00
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain2, "foo", serviceAccount)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Right(LdapUserDetails("", "", None, None, None)))
|
2018-07-27 10:18:29 -04:00
|
|
|
val authenticator =
|
|
|
|
new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
serviceAccount
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val response = authenticator.lookup("foo")
|
|
|
|
|
|
|
|
there.was(one(byDomainAuthenticator).lookup(testDomain1, "foo", serviceAccount))
|
|
|
|
there.was(one(byDomainAuthenticator).lookup(testDomain2, "foo", serviceAccount))
|
|
|
|
|
|
|
|
"and return None if authenticated" in {
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beRight
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
"return an error if all domains lookups fail but services are up" in {
|
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
val serviceAccount = mock[ServiceAccount]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "foo", serviceAccount)
|
|
|
|
.returns(Left(UserDoesNotExistException("first failed")))
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain2, "foo", serviceAccount)
|
|
|
|
.returns(Left(UserDoesNotExistException("second failed")))
|
|
|
|
val authenticator =
|
|
|
|
new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
serviceAccount
|
|
|
|
)
|
2019-06-11 12:15:52 -04:00
|
|
|
|
|
|
|
val response = authenticator.lookup("foo")
|
|
|
|
|
|
|
|
there.was(one(byDomainAuthenticator).lookup(testDomain1, "foo", serviceAccount))
|
|
|
|
there.was(one(byDomainAuthenticator).lookup(testDomain2, "foo", serviceAccount))
|
|
|
|
|
|
|
|
"and return error message" in {
|
|
|
|
response must beLeft
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
"return error if all lookups fail with at least one LDAP connectivity issue" in {
|
2018-07-27 10:18:29 -04:00
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
val serviceAccount = mock[ServiceAccount]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "foo", serviceAccount)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(LdapServiceException("some LDAP connectivity issue")))
|
2018-07-27 10:18:29 -04:00
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain2, "foo", serviceAccount)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(UserDoesNotExistException("second failed")))
|
2018-07-27 10:18:29 -04:00
|
|
|
val authenticator =
|
|
|
|
new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
serviceAccount
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val response = authenticator.lookup("foo")
|
|
|
|
|
|
|
|
there.was(one(byDomainAuthenticator).lookup(testDomain1, "foo", serviceAccount))
|
|
|
|
there.was(one(byDomainAuthenticator).lookup(testDomain2, "foo", serviceAccount))
|
|
|
|
|
|
|
|
"and return error message" in {
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beLeft
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-12-10 09:57:24 -05:00
|
|
|
".healthCheck" should {
|
|
|
|
"fail if there is some unexpected error" in {
|
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
val serviceAccount = mock[ServiceAccount]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "healthlookup", serviceAccount)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(LdapServiceException("some failure")))
|
2018-12-10 09:57:24 -05:00
|
|
|
val authenticator =
|
|
|
|
new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
serviceAccount
|
|
|
|
)
|
2018-12-10 09:57:24 -05:00
|
|
|
|
|
|
|
authenticator.healthCheck()
|
|
|
|
val response = authenticator.healthCheck().unsafeRunSync()
|
|
|
|
|
|
|
|
response should beLeft[HealthCheckError]
|
|
|
|
}
|
|
|
|
"succeed if the dummy user cant be found" in {
|
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
val serviceAccount = mock[ServiceAccount]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "healthlookup", serviceAccount)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(UserDoesNotExistException("does not exist")))
|
2018-12-10 09:57:24 -05:00
|
|
|
val authenticator =
|
|
|
|
new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
serviceAccount
|
|
|
|
)
|
2018-12-10 09:57:24 -05:00
|
|
|
|
|
|
|
authenticator.healthCheck()
|
|
|
|
val response = authenticator.healthCheck().unsafeRunSync()
|
|
|
|
|
|
|
|
response should beRight[Unit]
|
|
|
|
}
|
|
|
|
"succeed if the dummy user can be found" in {
|
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
val serviceAccount = mock[ServiceAccount]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "healthlookup", serviceAccount)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Right(LdapUserDetails("", "", None, None, None)))
|
2018-12-10 09:57:24 -05:00
|
|
|
val authenticator =
|
|
|
|
new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
2019-11-11 13:11:41 -05:00
|
|
|
serviceAccount
|
|
|
|
)
|
2018-12-10 09:57:24 -05:00
|
|
|
|
|
|
|
authenticator.healthCheck()
|
|
|
|
val response = authenticator.healthCheck().unsafeRunSync()
|
|
|
|
|
|
|
|
response should beRight[Unit]
|
|
|
|
}
|
|
|
|
}
|
2019-07-01 19:30:45 -04:00
|
|
|
".getUsersNotInLdap" should {
|
|
|
|
"return a list of users not found in LDAP" in {
|
|
|
|
val byDomainAuthenticator = mock[LdapByDomainAuthenticator]
|
|
|
|
val serviceAccount = mock[ServiceAccount]
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "does-not-exist", serviceAccount)
|
|
|
|
.returns(Left(UserDoesNotExistException("does not exist")))
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain2, "does-not-exist", serviceAccount)
|
|
|
|
.returns(Left(UserDoesNotExistException("does not exist")))
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "existing-user", serviceAccount)
|
|
|
|
.returns(Right(LdapUserDetails("", "", None, None, None)))
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, "another-existing-user", serviceAccount)
|
|
|
|
.returns(Left(UserDoesNotExistException("does not exist")))
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain2, "another-existing-user", serviceAccount)
|
|
|
|
.returns(Right(LdapUserDetails("", "", None, None, None)))
|
|
|
|
byDomainAuthenticator
|
|
|
|
.lookup(testDomain1, serviceAccount.name, serviceAccount)
|
|
|
|
.returns(Right(LdapUserDetails("", "", None, None, None)))
|
|
|
|
val authenticator =
|
|
|
|
new LdapAuthenticator(
|
|
|
|
List(testDomain1, testDomain2),
|
|
|
|
byDomainAuthenticator,
|
|
|
|
serviceAccount
|
|
|
|
)
|
|
|
|
|
|
|
|
authenticator
|
|
|
|
.getUsersNotInLdap(
|
|
|
|
List(
|
|
|
|
nonexistentUser,
|
|
|
|
nonexistentUser.copy(userName = "existing-user"),
|
2019-11-11 13:11:41 -05:00
|
|
|
nonexistentUser.copy(userName = "another-existing-user")
|
|
|
|
)
|
|
|
|
)
|
2019-07-01 19:30:45 -04:00
|
|
|
.unsafeRunSync() must
|
|
|
|
beEqualTo(List(nonexistentUser))
|
|
|
|
}
|
|
|
|
}
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
"LdapByDomainAuthenticator" should {
|
|
|
|
"return an error message if authenticated but no LDAP record is found" in {
|
|
|
|
val mocks = createMocks
|
|
|
|
mocks.searchResults.hasMore.returns(false)
|
|
|
|
val response = mocks.byDomainAuthenticator.authenticate(testDomain1, "foo", "bar")
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beLeft
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
"if creating context succeeds" in {
|
|
|
|
"and result.hasMore is true" in {
|
|
|
|
val mocks = createMocks
|
|
|
|
val response = mocks.byDomainAuthenticator.authenticate(testDomain1, "foo", "bar")
|
|
|
|
|
|
|
|
"return Success" in {
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beRight
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
"call contextCreator.apply" in {
|
2019-10-08 19:13:15 -04:00
|
|
|
// We first authenticate to the service account, and then to the user
|
|
|
|
there.was(
|
|
|
|
one(mocks.contextCreator).apply("test\\test", "test"),
|
2019-11-11 13:11:41 -05:00
|
|
|
one(mocks.contextCreator).apply("", "bar")
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
"call the correct search on context" in {
|
|
|
|
//because specs2 doesn't compare SearchControls objects properly, we have to use argument capture. And since
|
|
|
|
//mockito only allows all raw variable comparisons or mock variable comparisons, had to use argument
|
|
|
|
//capture on everything
|
|
|
|
val baseNameCapture = new ArgumentCapture[String]
|
|
|
|
val usernameFilterCapture = new ArgumentCapture[String]
|
|
|
|
val searchControlCapture = new ArgumentCapture[SearchControls]
|
|
|
|
|
|
|
|
there.was(
|
2019-11-11 13:11:41 -05:00
|
|
|
one(mocks.context).search(baseNameCapture, usernameFilterCapture, searchControlCapture)
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
searchControlCapture.value.getSearchScope mustEqual 2
|
|
|
|
baseNameCapture.value mustEqual "DC=test,DC=test,DC=com"
|
|
|
|
usernameFilterCapture.value mustEqual "(sAMAccountName=foo)"
|
|
|
|
}
|
|
|
|
|
|
|
|
"call result.hasMore" in {
|
|
|
|
there.was(one(mocks.searchResults).hasMore)
|
|
|
|
}
|
|
|
|
|
|
|
|
"call result.next" in {
|
|
|
|
there.was(one(mocks.searchResults).next())
|
|
|
|
}
|
|
|
|
|
|
|
|
"call result.next.getNameInNamespace" in {
|
|
|
|
there.was(one(mocks.searchNext).getNameInNamespace)
|
|
|
|
}
|
|
|
|
|
|
|
|
"call result.next.getAttributes" in {
|
|
|
|
there.was(one(mocks.searchNext).getAttributes)
|
|
|
|
}
|
|
|
|
|
|
|
|
"call attributes.get for username, email, firstname and lastname" in {
|
|
|
|
there.was(one(mocks.attributes).get("sAMAccountName"))
|
|
|
|
there.was(one(mocks.attributes).get("mail"))
|
|
|
|
there.was(one(mocks.attributes).get("givenName"))
|
|
|
|
there.was(one(mocks.attributes).get("sn"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
"and result.hasMore is false" in {
|
|
|
|
val mocks = createMocks
|
|
|
|
mocks.searchResults.hasMore.returns(false)
|
|
|
|
val response = mocks.byDomainAuthenticator.authenticate(testDomain1, "foo", "bar")
|
|
|
|
|
|
|
|
"return a UserDoesNotExistException" in {
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beLeft[LdapException]
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
"if creating the context fails" in {
|
|
|
|
val mocks = createMocks
|
|
|
|
mocks.contextCreator
|
|
|
|
.apply(anyString, anyString)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(LdapServiceException("oops")))
|
2018-07-27 10:18:29 -04:00
|
|
|
val response = mocks.byDomainAuthenticator.authenticate(testDomain1, "foo", "bar")
|
|
|
|
|
|
|
|
"return an error" in {
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beLeft
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
"lookup a user" should {
|
|
|
|
"return a user it can find" in {
|
|
|
|
val mocks = createMocks
|
2018-12-10 09:57:24 -05:00
|
|
|
val serviceAccount = ServiceAccount("second", "serviceuser", "servicepass")
|
2018-07-27 10:18:29 -04:00
|
|
|
val response = mocks.byDomainAuthenticator.lookup(testDomain1, "foo", serviceAccount)
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beRight
|
2018-07-27 10:18:29 -04:00
|
|
|
"call contextCreator.apply" in {
|
2018-12-10 09:57:24 -05:00
|
|
|
there.was(one(mocks.contextCreator).apply("second\\serviceuser", "servicepass"))
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
"call the correct search on context" in {
|
|
|
|
//because specs2 doesn't compare SearchControls objects properly, we have to use argument capture. And since
|
|
|
|
//mockito only allows all raw variable comparisons or mock variable comparisons, had to use argument
|
|
|
|
//capture on everything
|
|
|
|
val baseNameCapture = new ArgumentCapture[String]
|
|
|
|
val usernameFilterCapture = new ArgumentCapture[String]
|
|
|
|
val searchControlCapture = new ArgumentCapture[SearchControls]
|
|
|
|
|
|
|
|
there.was(
|
2019-11-11 13:11:41 -05:00
|
|
|
one(mocks.context).search(baseNameCapture, usernameFilterCapture, searchControlCapture)
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
searchControlCapture.value.getSearchScope mustEqual 2
|
|
|
|
baseNameCapture.value mustEqual "DC=test,DC=test,DC=com"
|
|
|
|
usernameFilterCapture.value mustEqual "(sAMAccountName=foo)"
|
|
|
|
}
|
|
|
|
|
|
|
|
"call result.hasMore" in {
|
|
|
|
there.was(one(mocks.searchResults).hasMore)
|
|
|
|
}
|
|
|
|
|
|
|
|
"call result.next" in {
|
|
|
|
there.was(one(mocks.searchResults).next())
|
|
|
|
}
|
|
|
|
|
|
|
|
"call result.next.getNameInNamespace" in {
|
|
|
|
there.was(one(mocks.searchNext).getNameInNamespace)
|
|
|
|
}
|
|
|
|
|
|
|
|
"call result.next.getAttributes" in {
|
|
|
|
there.was(one(mocks.searchNext).getAttributes)
|
|
|
|
}
|
|
|
|
|
|
|
|
"call attributes.get for username, email, firstname and lastname" in {
|
|
|
|
there.was(one(mocks.attributes).get("sAMAccountName"))
|
|
|
|
there.was(one(mocks.attributes).get("mail"))
|
|
|
|
there.was(one(mocks.attributes).get("givenName"))
|
|
|
|
there.was(one(mocks.attributes).get("sn"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"return a Failure if the user does not exist" in {
|
|
|
|
val mocks = createMocks
|
2018-12-10 09:57:24 -05:00
|
|
|
val serviceAccount = ServiceAccount("first", "foo", "bar")
|
2018-07-27 10:18:29 -04:00
|
|
|
mocks.searchResults.hasMore.returns(false)
|
|
|
|
val response = mocks.byDomainAuthenticator.lookup(testDomain1, "foo", serviceAccount)
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beLeft
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
"return a Failure when the service user credentials are incorrect" in {
|
|
|
|
val mocks = createMocks
|
2018-12-10 09:57:24 -05:00
|
|
|
val serviceAccount = ServiceAccount("first", "foo", "bar")
|
2018-07-27 10:18:29 -04:00
|
|
|
mocks.contextCreator
|
|
|
|
.apply(anyString, anyString)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(LdapServiceException("oops")))
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val response = mocks.byDomainAuthenticator.lookup(testDomain1, "foo", serviceAccount)
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
response must beLeft
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"TestAuthenticator" should {
|
|
|
|
"authenticate the test user" in {
|
|
|
|
val mockLdapAuth = mock[LdapAuthenticator]
|
|
|
|
mockLdapAuth
|
|
|
|
.authenticate(anyString, anyString)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(UserDoesNotExistException("should not be here")))
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val underTest = new TestAuthenticator(mockLdapAuth)
|
2019-07-01 19:30:45 -04:00
|
|
|
val testUserLookup = underTest.authenticate("testuser", "testpassword")
|
2018-07-27 10:18:29 -04:00
|
|
|
|
2019-07-01 19:30:45 -04:00
|
|
|
testUserLookup must beRight(
|
2019-02-19 11:34:45 -05:00
|
|
|
LdapUserDetails(
|
2018-07-27 10:18:29 -04:00
|
|
|
"O=test,OU=testdata,CN=testuser",
|
|
|
|
"testuser",
|
|
|
|
Some("test@test.test"),
|
|
|
|
Some("Test"),
|
2019-11-11 13:11:41 -05:00
|
|
|
Some("User")
|
|
|
|
)
|
|
|
|
)
|
2019-07-01 19:30:45 -04:00
|
|
|
|
|
|
|
val recordPagingUserLookup = underTest.lookup("recordPagingTestUser")
|
|
|
|
|
|
|
|
recordPagingUserLookup must beRight(
|
|
|
|
LdapUserDetails(
|
|
|
|
"O=test,OU=testdata,CN=recordPagingTestUser",
|
|
|
|
"recordPagingTestUser",
|
|
|
|
Some("test@test.test"),
|
|
|
|
Some("Test"),
|
2019-11-11 13:11:41 -05:00
|
|
|
Some("User")
|
|
|
|
)
|
|
|
|
)
|
2018-07-27 10:18:29 -04:00
|
|
|
there.were(noCallsTo(mockLdapAuth))
|
|
|
|
}
|
2019-06-11 12:15:52 -04:00
|
|
|
"authenticate the record paging test user" in {
|
|
|
|
val mockLdapAuth = mock[LdapAuthenticator]
|
|
|
|
mockLdapAuth
|
|
|
|
.authenticate(anyString, anyString)
|
|
|
|
.returns(Left(UserDoesNotExistException("should not be here")))
|
|
|
|
|
|
|
|
val underTest = new TestAuthenticator(mockLdapAuth)
|
|
|
|
val result = underTest.authenticate("recordPagingTestUser", "testpassword")
|
|
|
|
|
|
|
|
result must beRight(
|
|
|
|
LdapUserDetails(
|
|
|
|
"O=test,OU=testdata,CN=recordPagingTestUser",
|
|
|
|
"recordPagingTestUser",
|
|
|
|
Some("test@test.test"),
|
|
|
|
Some("Test"),
|
2019-11-11 13:11:41 -05:00
|
|
|
Some("User")
|
|
|
|
)
|
|
|
|
)
|
2019-06-11 12:15:52 -04:00
|
|
|
there.were(noCallsTo(mockLdapAuth))
|
|
|
|
}
|
2018-07-27 10:18:29 -04:00
|
|
|
"authenticate a user that is not the test user" in {
|
|
|
|
val mockLdapAuth = mock[LdapAuthenticator]
|
2019-02-19 11:34:45 -05:00
|
|
|
val userDetails =
|
|
|
|
LdapUserDetails("o=foo,cn=bar", "foo", Some("bar"), Some("baz"), Some("qux"))
|
2019-06-11 12:15:52 -04:00
|
|
|
mockLdapAuth.authenticate(anyString, anyString).returns(Right(userDetails))
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val underTest = new TestAuthenticator(mockLdapAuth)
|
|
|
|
val result = underTest.authenticate("foo", "bar")
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
result must beRight(userDetails)
|
2018-07-27 10:18:29 -04:00
|
|
|
there.was(one(mockLdapAuth).authenticate("foo", "bar"))
|
|
|
|
}
|
|
|
|
"lookup the test user" in {
|
|
|
|
val mockLdapAuth = mock[LdapAuthenticator]
|
|
|
|
mockLdapAuth
|
|
|
|
.authenticate(anyString, anyString)
|
2019-06-11 12:15:52 -04:00
|
|
|
.returns(Left(UserDoesNotExistException("should not be here")))
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val underTest = new TestAuthenticator(mockLdapAuth)
|
2019-07-01 19:30:45 -04:00
|
|
|
val testUserLookup = underTest.lookup("testuser")
|
2018-07-27 10:18:29 -04:00
|
|
|
|
2019-07-01 19:30:45 -04:00
|
|
|
testUserLookup must beRight(
|
2019-02-19 11:34:45 -05:00
|
|
|
LdapUserDetails(
|
2018-07-27 10:18:29 -04:00
|
|
|
"O=test,OU=testdata,CN=testuser",
|
|
|
|
"testuser",
|
|
|
|
Some("test@test.test"),
|
|
|
|
Some("Test"),
|
2019-11-11 13:11:41 -05:00
|
|
|
Some("User")
|
|
|
|
)
|
|
|
|
)
|
2019-07-01 19:30:45 -04:00
|
|
|
|
|
|
|
val recordPagingUserLookup = underTest.lookup("recordPagingTestUser")
|
|
|
|
|
|
|
|
recordPagingUserLookup must beRight(
|
|
|
|
LdapUserDetails(
|
|
|
|
"O=test,OU=testdata,CN=recordPagingTestUser",
|
|
|
|
"recordPagingTestUser",
|
|
|
|
Some("test@test.test"),
|
|
|
|
Some("Test"),
|
2019-11-11 13:11:41 -05:00
|
|
|
Some("User")
|
|
|
|
)
|
|
|
|
)
|
2019-07-01 19:30:45 -04:00
|
|
|
|
2018-07-27 10:18:29 -04:00
|
|
|
there.were(noCallsTo(mockLdapAuth))
|
|
|
|
}
|
2019-06-11 12:15:52 -04:00
|
|
|
"lookup the record paging test user" in {
|
|
|
|
val mockLdapAuth = mock[LdapAuthenticator]
|
|
|
|
mockLdapAuth
|
|
|
|
.authenticate(anyString, anyString)
|
|
|
|
.returns(Left(UserDoesNotExistException("should not be here")))
|
|
|
|
|
|
|
|
val underTest = new TestAuthenticator(mockLdapAuth)
|
|
|
|
val result = underTest.lookup("recordPagingTestUser")
|
|
|
|
|
|
|
|
result must beRight(
|
|
|
|
LdapUserDetails(
|
|
|
|
"O=test,OU=testdata,CN=recordPagingTestUser",
|
|
|
|
"recordPagingTestUser",
|
|
|
|
Some("test@test.test"),
|
|
|
|
Some("Test"),
|
2019-11-11 13:11:41 -05:00
|
|
|
Some("User")
|
|
|
|
)
|
|
|
|
)
|
2019-06-11 12:15:52 -04:00
|
|
|
there.were(noCallsTo(mockLdapAuth))
|
|
|
|
}
|
2018-07-27 10:18:29 -04:00
|
|
|
"lookup a user that is not the test user" in {
|
|
|
|
val mockLdapAuth = mock[LdapAuthenticator]
|
2019-02-19 11:34:45 -05:00
|
|
|
val userDetails =
|
|
|
|
LdapUserDetails("o=foo,cn=bar", "foo", Some("bar"), Some("baz"), Some("qux"))
|
2019-06-11 12:15:52 -04:00
|
|
|
mockLdapAuth.lookup(anyString).returns(Right(userDetails))
|
2018-07-27 10:18:29 -04:00
|
|
|
|
|
|
|
val underTest = new TestAuthenticator(mockLdapAuth)
|
|
|
|
val result = underTest.lookup("foo")
|
|
|
|
|
2019-06-11 12:15:52 -04:00
|
|
|
result must beRight(userDetails)
|
2018-07-27 10:18:29 -04:00
|
|
|
there.was(one(mockLdapAuth).lookup("foo"))
|
|
|
|
}
|
2019-07-01 19:30:45 -04:00
|
|
|
"find non-existent users" in {
|
|
|
|
val mockLdapAuth = mock[LdapAuthenticator]
|
|
|
|
mockLdapAuth
|
|
|
|
.getUsersNotInLdap(List(nonexistentUser))
|
|
|
|
.returns(IO(List(nonexistentUser)))
|
|
|
|
|
|
|
|
val underTest = new TestAuthenticator(mockLdapAuth)
|
|
|
|
underTest.getUsersNotInLdap(List(nonexistentUser)).unsafeRunSync() must beEqualTo(
|
2019-11-11 13:11:41 -05:00
|
|
|
List(nonexistentUser)
|
|
|
|
)
|
2019-07-01 19:30:45 -04:00
|
|
|
}
|
|
|
|
"perform a health check" in {
|
|
|
|
val mockLdapAuth = mock[LdapAuthenticator]
|
|
|
|
mockLdapAuth.healthCheck().returns(IO(Right(())))
|
|
|
|
val underTest = new TestAuthenticator(mockLdapAuth)
|
|
|
|
|
|
|
|
underTest.healthCheck().unsafeRunSync() must beRight(())
|
|
|
|
}
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|
2019-06-11 12:15:52 -04:00
|
|
|
"return a successful health check" in {
|
|
|
|
val mockLdapAuth = mock[LdapAuthenticator]
|
2020-06-29 09:43:32 -05:00
|
|
|
mockLdapAuth.healthCheck().returns(IO(Right(())).asHealthCheck(classOf[LdapAuthenticatorSpec]))
|
2019-06-11 12:15:52 -04:00
|
|
|
|
|
|
|
new TestAuthenticator(mockLdapAuth).healthCheck().unsafeRunSync() should beRight[Unit]
|
|
|
|
}
|
2018-07-27 10:18:29 -04:00
|
|
|
}
|