diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c0e6a4ca..7e63090c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ -## 2.0 +## 3.0 + +- Update to Play 2.4 +- Fix a lot of inconsistencies in the API + +## 2.0 (2015-03-28) - Use lazy val to initialize SecureRandom, so that initialization occurs also async - Refactor authenticators and add BearerTokenAuthenticator, JWTAuthenticator, SessionAuthenticator and DummyAuthenticator diff --git a/silhouette-testkit/app/com/mohiva/play/silhouette/test/Fakes.scala b/silhouette-testkit/app/com/mohiva/play/silhouette/test/Fakes.scala index 8012803f2..6f25ab007 100644 --- a/silhouette-testkit/app/com/mohiva/play/silhouette/test/Fakes.scala +++ b/silhouette-testkit/app/com/mohiva/play/silhouette/test/Fakes.scala @@ -69,24 +69,35 @@ class FakeAuthenticatorDAO[T <: StorableAuthenticator] extends AuthenticatorDAO[ var data: mutable.HashMap[String, T] = mutable.HashMap() /** - * Saves the authenticator. + * Finds the authenticator for the given ID. * - * @param authenticator The authenticator to save. - * @return The saved auth authenticator. + * @param id The authenticator ID. + * @return The found authenticator or None if no authenticator could be found for the given ID. */ - def save(authenticator: T): Future[T] = { + def find(id: String): Future[Option[T]] = { + Future.successful(data.get(id)) + } + + /** + * Adds a new authenticator. + * + * @param authenticator The authenticator to add. + * @return The added authenticator. + */ + def add(authenticator: T): Future[T] = { data += (authenticator.id -> authenticator) Future.successful(authenticator) } /** - * Finds the authenticator for the given ID. + * Updates an already existing authenticator. * - * @param id The authenticator ID. - * @return The found authenticator or None if no authenticator could be found for the given ID. + * @param authenticator The authenticator to update. + * @return The updated authenticator. */ - def find(id: String): Future[Option[T]] = { - Future.successful(data.get(id)) + def update(authenticator: T): Future[T] = { + data += (authenticator.id -> authenticator) + Future.successful(authenticator) } /** diff --git a/silhouette-testkit/test/com/mohiva/play/silhouette/test/FakesSpec.scala b/silhouette-testkit/test/com/mohiva/play/silhouette/test/FakesSpec.scala index b3ff1b839..e56afac73 100644 --- a/silhouette-testkit/test/com/mohiva/play/silhouette/test/FakesSpec.scala +++ b/silhouette-testkit/test/com/mohiva/play/silhouette/test/FakesSpec.scala @@ -43,23 +43,13 @@ class FakesSpec extends PlaySpecification with JsonMatchers { } } - "The `save` method of the `FakeAuthenticatorDAO`" should { - "save an authenticator" in { - val loginInfo = LoginInfo("test", "test") - val authenticator = new FakeAuthenticator(loginInfo) - val dao = new FakeAuthenticatorDAO[FakeAuthenticator]() - - await(dao.save(authenticator)) must be equalTo authenticator - } - } - "The `find` method of the `FakeAuthenticatorDAO`" should { "return an authenticator for the given ID" in { val loginInfo = LoginInfo("test", "test") val authenticator = new FakeAuthenticator(loginInfo, "test") val dao = new FakeAuthenticatorDAO[FakeAuthenticator]() - await(dao.save(authenticator)) + await(dao.add(authenticator)) await(dao.find("test")) must beSome(authenticator) } @@ -71,13 +61,33 @@ class FakesSpec extends PlaySpecification with JsonMatchers { } } + "The `add` method of the `FakeAuthenticatorDAO`" should { + "add an authenticator" in { + val loginInfo = LoginInfo("test", "test") + val authenticator = new FakeAuthenticator(loginInfo) + val dao = new FakeAuthenticatorDAO[FakeAuthenticator]() + + await(dao.add(authenticator)) must be equalTo authenticator + } + } + + "The `update` method of the `FakeAuthenticatorDAO`" should { + "update an authenticator" in { + val loginInfo = LoginInfo("test", "test") + val authenticator = new FakeAuthenticator(loginInfo) + val dao = new FakeAuthenticatorDAO[FakeAuthenticator]() + + await(dao.update(authenticator)) must be equalTo authenticator + } + } + "The `remove` method of the `FakeAuthenticatorDAO`" should { "remove an authenticator" in { val loginInfo = LoginInfo("test", "test") val authenticator = new FakeAuthenticator(loginInfo, "test") val dao = new FakeAuthenticatorDAO[FakeAuthenticator]() - await(dao.save(authenticator)) + await(dao.add(authenticator)) await(dao.find("test")) must beSome(authenticator) await(dao.remove("test")) await(dao.find("test")) must beNone diff --git a/silhouette/app/com/mohiva/play/silhouette/api/AuthInfo.scala b/silhouette/app/com/mohiva/play/silhouette/api/AuthInfo.scala new file mode 100644 index 000000000..5bbd2cd6c --- /dev/null +++ b/silhouette/app/com/mohiva/play/silhouette/api/AuthInfo.scala @@ -0,0 +1,21 @@ +/** + * Copyright 2015 Mohiva Organisation (license at mohiva dot com) + * + * 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 com.mohiva.play.silhouette.api + +/** + * A marker trait for authentication information. + */ +trait AuthInfo diff --git a/silhouette/app/com/mohiva/play/silhouette/api/repositories/AuthInfoRepository.scala b/silhouette/app/com/mohiva/play/silhouette/api/repositories/AuthInfoRepository.scala new file mode 100644 index 000000000..5baaccaf9 --- /dev/null +++ b/silhouette/app/com/mohiva/play/silhouette/api/repositories/AuthInfoRepository.scala @@ -0,0 +1,71 @@ +/** + * Copyright 2015 Mohiva Organisation (license at mohiva dot com) + * + * 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 com.mohiva.play.silhouette.api.repositories + +import com.mohiva.play.silhouette.api.{ AuthInfo, LoginInfo } + +import scala.concurrent.Future +import scala.reflect.ClassTag + +/** + * A trait that provides the means to persist authentication information for the Silhouette module. + * + * If the application supports the concept of "merged identities", i.e., the same user being + * able to authenticate through different providers, then make sure that the auth info for + * every linked login info gets stored separately. + */ +trait AuthInfoRepository { + + /** + * Finds the auth info which is linked with the specified login info. + * + * @param loginInfo The linked login info. + * @param tag The class tag of the auth info. + * @tparam T The type of the auth info to handle. + * @return The found auth info or None if no auth info could be found for the given login info. + */ + def find[T <: AuthInfo](loginInfo: LoginInfo)(implicit tag: ClassTag[T]): Future[Option[T]] + + /** + * Adds new auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be saved. + * @param authInfo The auth info to save. + * @tparam T The type of the auth info to handle. + * @return The saved auth info. + */ + def add[T <: AuthInfo](loginInfo: LoginInfo, authInfo: T): Future[T] + + /** + * Updates the auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be updated. + * @param authInfo The auth info to update. + * @tparam T The type of the auth info to handle. + * @return The updated auth info. + */ + def update[T <: AuthInfo](loginInfo: LoginInfo, authInfo: T): Future[T] + + /** + * Removes the auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be removed. + * @param tag The class tag of the auth info. + * @tparam T The type of the auth info to handle. + * @return A future to wait for the process to be completed. + */ + def remove[T <: AuthInfo](loginInfo: LoginInfo)(implicit tag: ClassTag[T]): Future[Unit] +} diff --git a/silhouette/app/com/mohiva/play/silhouette/api/repositories/package.scala b/silhouette/app/com/mohiva/play/silhouette/api/repositories/package.scala new file mode 100644 index 000000000..277a52e7b --- /dev/null +++ b/silhouette/app/com/mohiva/play/silhouette/api/repositories/package.scala @@ -0,0 +1,21 @@ +/** + * Copyright 2015 Mohiva Organisation (license at mohiva dot com) + * + * 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 com.mohiva.play.silhouette.api + +/** + * Provides repositories used by the API to persist entities. + */ +package object repositories {} diff --git a/silhouette/app/com/mohiva/play/silhouette/api/services/AuthInfoService.scala b/silhouette/app/com/mohiva/play/silhouette/api/services/AuthInfoService.scala deleted file mode 100644 index fee8fc2fa..000000000 --- a/silhouette/app/com/mohiva/play/silhouette/api/services/AuthInfoService.scala +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Original work: SecureSocial (https://github.com/jaliss/securesocial) - * Copyright 2013 Jorge Aliss (jaliss at gmail dot com) - twitter: @jaliss - * - * Derivative work: Silhouette (https://github.com/mohiva/play-silhouette) - * Modifications Copyright 2015 Mohiva Organisation (license at mohiva dot com) - * - * 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 com.mohiva.play.silhouette.api.services - -import com.mohiva.play.silhouette.api.LoginInfo - -import scala.concurrent.Future -import scala.reflect.ClassTag - -/** - * A trait that provides the means to find and save auth information - * for the Silhouette module. - */ -trait AuthInfoService { - - /** - * Saves auth info. - * - * This method gets called when a user logs in (social auth) or registers. This is the chance - * to persist the auth info for a provider in the backing store. If the application supports - * the concept of "merged identities", i.e., the same user being able to authenticate through - * different providers, then make sure that the auth info for every linked login info gets - * stored separately. - * - * @param loginInfo The login info for which the auth info should be saved. - * @param authInfo The auth info to save. - * @return The saved auth info. - */ - def save[T <: AuthInfo](loginInfo: LoginInfo, authInfo: T): Future[T] - - /** - * Retrieves the auth info which is linked with the specified login info. - * - * @param loginInfo The linked login info. - * @param tag The class tag of the auth info. - * @return The retrieved auth info or None if no auth info could be retrieved for the given login info. - */ - def retrieve[T <: AuthInfo](loginInfo: LoginInfo)(implicit tag: ClassTag[T]): Future[Option[T]] -} - -/** - * A marker trait for authentication information. - */ -trait AuthInfo diff --git a/silhouette/app/com/mohiva/play/silhouette/api/util/CacheLayer.scala b/silhouette/app/com/mohiva/play/silhouette/api/util/CacheLayer.scala index 66283c71c..f55a646da 100644 --- a/silhouette/app/com/mohiva/play/silhouette/api/util/CacheLayer.scala +++ b/silhouette/app/com/mohiva/play/silhouette/api/util/CacheLayer.scala @@ -16,6 +16,7 @@ package com.mohiva.play.silhouette.api.util import scala.concurrent.Future +import scala.concurrent.duration.Duration import scala.reflect.ClassTag /** @@ -23,16 +24,6 @@ import scala.reflect.ClassTag */ trait CacheLayer { - /** - * Save a value in cache. - * - * @param key The item key under which the value should be saved. - * @param value The value to save. - * @param expiration Expiration time in seconds (0 second means eternity). - * @return The value saved in cache. - */ - def save[T](key: String, value: T, expiration: Int = 0): Future[T] - /** * Finds a value in the cache. * @@ -42,6 +33,16 @@ trait CacheLayer { */ def find[T: ClassTag](key: String): Future[Option[T]] + /** + * Save a value in cache. + * + * @param key The item key under which the value should be saved. + * @param value The value to save. + * @param expiration Expiration time in seconds (0 second means eternity). + * @return The value saved in cache. + */ + def save[T](key: String, value: T, expiration: Duration = Duration.Inf): Future[T] + /** * Remove a value from the cache. * diff --git a/silhouette/app/com/mohiva/play/silhouette/api/util/PasswordHasher.scala b/silhouette/app/com/mohiva/play/silhouette/api/util/PasswordHasher.scala index 79aca277c..ea706e64a 100644 --- a/silhouette/app/com/mohiva/play/silhouette/api/util/PasswordHasher.scala +++ b/silhouette/app/com/mohiva/play/silhouette/api/util/PasswordHasher.scala @@ -19,7 +19,7 @@ */ package com.mohiva.play.silhouette.api.util -import com.mohiva.play.silhouette.api.services.AuthInfo +import com.mohiva.play.silhouette.api.AuthInfo /** * The password details. diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/BearerTokenAuthenticator.scala b/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/BearerTokenAuthenticator.scala index 9a6b3f1b1..3ce0ad226 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/BearerTokenAuthenticator.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/BearerTokenAuthenticator.scala @@ -142,7 +142,7 @@ class BearerTokenAuthenticatorService( * @return The serialized authenticator value. */ def init(authenticator: BearerTokenAuthenticator)(implicit request: RequestHeader) = { - dao.save(authenticator).map { a => + dao.add(authenticator).map { a => a.id }.recover { case e => throw new AuthenticatorInitializationException(InitError.format(ID, authenticator), e) @@ -199,7 +199,7 @@ class BearerTokenAuthenticatorService( * @return The original or a manipulated result. */ def update(authenticator: BearerTokenAuthenticator, result: Result)(implicit request: RequestHeader) = { - dao.save(authenticator).map { a => + dao.update(authenticator).map { a => AuthenticatorResult(result) }.recover { case e => throw new AuthenticatorUpdateException(UpdateError.format(ID, authenticator), e) diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/CookieAuthenticator.scala b/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/CookieAuthenticator.scala index bf8010fca..dba2a7df2 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/CookieAuthenticator.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/CookieAuthenticator.scala @@ -165,7 +165,7 @@ class CookieAuthenticatorService( * @return The serialized authenticator value. */ def init(authenticator: CookieAuthenticator)(implicit request: RequestHeader) = { - dao.save(authenticator).map { a => + dao.add(authenticator).map { a => Cookie( name = settings.cookieName, value = a.id, @@ -231,7 +231,7 @@ class CookieAuthenticatorService( * @return The original or a manipulated result. */ def update(authenticator: CookieAuthenticator, result: Result)(implicit request: RequestHeader) = { - dao.save(authenticator).map { a => + dao.update(authenticator).map { a => AuthenticatorResult(result) }.recover { case e => throw new AuthenticatorUpdateException(UpdateError.format(ID, authenticator), e) diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/JWTAuthenticator.scala b/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/JWTAuthenticator.scala index 6a93de3ab..8cdbc27ac 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/JWTAuthenticator.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/authenticators/JWTAuthenticator.scala @@ -298,7 +298,7 @@ class JWTAuthenticatorService( * @return The serialized authenticator value. */ def init(authenticator: JWTAuthenticator)(implicit request: RequestHeader) = { - dao.fold(Future.successful(authenticator))(_.save(authenticator)).map { a => + dao.fold(Future.successful(authenticator))(_.add(authenticator)).map { a => serialize(a)(settings) }.recover { case e => throw new AuthenticatorInitializationException(InitError.format(ID, authenticator), e) @@ -354,7 +354,7 @@ class JWTAuthenticatorService( * @return The original or a manipulated result. */ def update(authenticator: JWTAuthenticator, result: Result)(implicit request: RequestHeader) = { - dao.fold(Future.successful(authenticator))(_.save(authenticator)).map { a => + dao.fold(Future.successful(authenticator))(_.update(authenticator)).map { a => AuthenticatorResult(result.withHeaders(settings.headerName -> serialize(a)(settings))) }.recover { case e => throw new AuthenticatorUpdateException(UpdateError.format(ID, authenticator), e) diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/daos/AuthInfoDAO.scala b/silhouette/app/com/mohiva/play/silhouette/impl/daos/AuthInfoDAO.scala index e263d10a3..951b0cb1e 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/daos/AuthInfoDAO.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/daos/AuthInfoDAO.scala @@ -15,8 +15,7 @@ */ package com.mohiva.play.silhouette.impl.daos -import com.mohiva.play.silhouette.api.LoginInfo -import com.mohiva.play.silhouette.api.services.AuthInfo +import com.mohiva.play.silhouette.api.{ AuthInfo, LoginInfo } import scala.concurrent.Future @@ -28,19 +27,36 @@ import scala.concurrent.Future trait AuthInfoDAO[T <: AuthInfo] { /** - * Saves the auth info. + * Finds the auth info which is linked to the specified login info. * - * @param loginInfo The login info for which the auth info should be saved. - * @param authInfo The auth info to save. - * @return The saved auth info. + * @param loginInfo The linked login info. + * @return The found auth info or None if no auth info could be found for the given login info. */ - def save(loginInfo: LoginInfo, authInfo: T): Future[T] + def find(loginInfo: LoginInfo): Future[Option[T]] /** - * Finds the auth info which is linked with the specified login info. + * Adds new auth info for the given login info. * - * @param loginInfo The linked login info. - * @return The found auth info or None if no auth info could be found for the given login info. + * @param loginInfo The login info for which the auth info should be added. + * @param authInfo The auth info to add. + * @return The added auth info. */ - def find(loginInfo: LoginInfo): Future[Option[T]] + def add(loginInfo: LoginInfo, authInfo: T): Future[T] + + /** + * Updates the auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be updated. + * @param authInfo The auth info to update. + * @return The updated auth info. + */ + def update(loginInfo: LoginInfo, authInfo: T): Future[T] + + /** + * Removes the auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be removed. + * @return A future to wait for the process to be completed. + */ + def remove(loginInfo: LoginInfo): Future[Unit] } diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/daos/AuthenticatorDAO.scala b/silhouette/app/com/mohiva/play/silhouette/impl/daos/AuthenticatorDAO.scala index 78daf4f13..1be9069c9 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/daos/AuthenticatorDAO.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/daos/AuthenticatorDAO.scala @@ -26,14 +26,6 @@ import scala.concurrent.Future */ trait AuthenticatorDAO[T <: StorableAuthenticator] { - /** - * Saves the authenticator. - * - * @param authenticator The authenticator to save. - * @return The saved authenticator. - */ - def save(authenticator: T): Future[T] - /** * Finds the authenticator for the given ID. * @@ -42,6 +34,22 @@ trait AuthenticatorDAO[T <: StorableAuthenticator] { */ def find(id: String): Future[Option[T]] + /** + * Adds a new authenticator. + * + * @param authenticator The authenticator to add. + * @return The added authenticator. + */ + def add(authenticator: T): Future[T] + + /** + * Updates an already existing authenticator. + * + * @param authenticator The authenticator to update. + * @return The updated authenticator. + */ + def update(authenticator: T): Future[T] + /** * Removes the authenticator for the given ID. * diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/daos/CacheAuthenticatorDAO.scala b/silhouette/app/com/mohiva/play/silhouette/impl/daos/CacheAuthenticatorDAO.scala index 5aaddbfcc..20b0723ed 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/daos/CacheAuthenticatorDAO.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/daos/CacheAuthenticatorDAO.scala @@ -19,6 +19,7 @@ import com.mohiva.play.silhouette.api.StorableAuthenticator import com.mohiva.play.silhouette.api.util.CacheLayer import scala.concurrent.Future +import scala.concurrent.duration.Duration import scala.reflect.ClassTag /** @@ -29,14 +30,6 @@ import scala.reflect.ClassTag */ class CacheAuthenticatorDAO[T <: StorableAuthenticator: ClassTag](cacheLayer: CacheLayer) extends AuthenticatorDAO[T] { - /** - * Saves the authenticator. - * - * @param authenticator The authenticator to save. - * @return The saved auth info. - */ - def save(authenticator: T): Future[T] = cacheLayer.save[T](authenticator.id, authenticator) - /** * Finds the authenticator for the given ID. * @@ -45,6 +38,22 @@ class CacheAuthenticatorDAO[T <: StorableAuthenticator: ClassTag](cacheLayer: Ca */ def find(id: String): Future[Option[T]] = cacheLayer.find[T](id) + /** + * Adds a new authenticator. + * + * @param authenticator The authenticator to add. + * @return The added authenticator. + */ + def add(authenticator: T): Future[T] = cacheLayer.save[T](authenticator.id, authenticator, Duration.Inf) + + /** + * Updates an already existing authenticator. + * + * @param authenticator The authenticator to update. + * @return The updated authenticator. + */ + def update(authenticator: T): Future[T] = cacheLayer.save[T](authenticator.id, authenticator, Duration.Inf) + /** * Removes the authenticator for the given ID. * diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/daos/DelegableAuthInfoDAO.scala b/silhouette/app/com/mohiva/play/silhouette/impl/daos/DelegableAuthInfoDAO.scala index a1835b7e9..60a1c3675 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/daos/DelegableAuthInfoDAO.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/daos/DelegableAuthInfoDAO.scala @@ -15,7 +15,7 @@ */ package com.mohiva.play.silhouette.impl.daos -import com.mohiva.play.silhouette.api.services.AuthInfo +import com.mohiva.play.silhouette.api.AuthInfo import scala.reflect.ClassTag @@ -24,8 +24,8 @@ import scala.reflect.ClassTag * * This abstract implementation of the [[com.mohiva.play.silhouette.impl.daos.AuthInfoDAO]] trait * allows us to get the class tag of the auth info it is responsible for. Based on the class tag - * the [[com.mohiva.play.silhouette.impl.services.DelegableAuthInfoService]] class can delegate - * operations to the DAO which is responsible for the currently handled auth info. + * the [[com.mohiva.play.silhouette.impl.repositories.DelegableAuthInfoRepository]] class can + * delegate operations to the DAO which is responsible for the currently handled auth info. * * @param classTag The class tag for the type parameter. * @tparam T The type of the auth info to store. diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/providers/BasicAuthProvider.scala b/silhouette/app/com/mohiva/play/silhouette/impl/providers/BasicAuthProvider.scala index 57bb0188e..db95a0e20 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/providers/BasicAuthProvider.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/providers/BasicAuthProvider.scala @@ -16,7 +16,7 @@ package com.mohiva.play.silhouette.impl.providers import com.mohiva.play.silhouette.api.exceptions.ConfigurationException -import com.mohiva.play.silhouette.api.services.AuthInfoService +import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository import com.mohiva.play.silhouette.api.util._ import com.mohiva.play.silhouette.api.{ LoginInfo, RequestProvider } import com.mohiva.play.silhouette.impl.exceptions.{ IdentityNotFoundException, InvalidPasswordException } @@ -36,12 +36,12 @@ import scala.concurrent.Future * the application has changed the hashing algorithm, the provider hashes the entered password again with the new * algorithm and stores the auth info in the backing store. * - * @param authInfoService The auth info service. + * @param authInfoRepository The auth info repository. * @param passwordHasher The default password hasher used by the application. * @param passwordHasherList List of password hasher supported by the application. */ class BasicAuthProvider( - authInfoService: AuthInfoService, + authInfoRepository: AuthInfoRepository, passwordHasher: PasswordHasher, passwordHasherList: Seq[PasswordHasher]) extends RequestProvider { @@ -63,11 +63,11 @@ class BasicAuthProvider( getCredentials(request) match { case Some(credentials) => val loginInfo = LoginInfo(id, credentials.identifier) - authInfoService.retrieve[PasswordInfo](loginInfo).map { + authInfoRepository.find[PasswordInfo](loginInfo).map { case Some(authInfo) => passwordHasherList.find(_.id == authInfo.hasher) match { case Some(hasher) if hasher.matches(authInfo, credentials.password) => if (hasher != passwordHasher) { - authInfoService.save(loginInfo, passwordHasher.hash(credentials.password)) + authInfoRepository.update(loginInfo, passwordHasher.hash(credentials.password)) } Some(loginInfo) case Some(hasher) => throw new InvalidPasswordException(InvalidPassword.format(id)) diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/providers/CredentialsProvider.scala b/silhouette/app/com/mohiva/play/silhouette/impl/providers/CredentialsProvider.scala index 1381f2897..2d2427904 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/providers/CredentialsProvider.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/providers/CredentialsProvider.scala @@ -21,7 +21,7 @@ package com.mohiva.play.silhouette.impl.providers import com.mohiva.play.silhouette.api._ import com.mohiva.play.silhouette.api.exceptions.ConfigurationException -import com.mohiva.play.silhouette.api.services.AuthInfoService +import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository import com.mohiva.play.silhouette.api.util.{ Credentials, PasswordHasher, PasswordInfo } import com.mohiva.play.silhouette.impl.exceptions.{ IdentityNotFoundException, InvalidPasswordException } import com.mohiva.play.silhouette.impl.providers.CredentialsProvider._ @@ -38,12 +38,12 @@ import scala.concurrent.Future * the application has changed the hashing algorithm, the provider hashes the entered password again with the new * algorithm and stores the auth info in the backing store. * - * @param authInfoService The auth info service. + * @param authInfoRepository The auth info repository. * @param passwordHasher The default password hasher used by the application. * @param passwordHasherList List of password hasher supported by the application. */ class CredentialsProvider( - authInfoService: AuthInfoService, + authInfoRepository: AuthInfoRepository, passwordHasher: PasswordHasher, passwordHasherList: Seq[PasswordHasher]) extends Provider { @@ -62,11 +62,11 @@ class CredentialsProvider( */ def authenticate(credentials: Credentials): Future[LoginInfo] = { val loginInfo = LoginInfo(id, credentials.identifier) - authInfoService.retrieve[PasswordInfo](loginInfo).map { + authInfoRepository.find[PasswordInfo](loginInfo).map { case Some(authInfo) => passwordHasherList.find(_.id == authInfo.hasher) match { case Some(hasher) if hasher.matches(authInfo, credentials.password) => if (hasher != passwordHasher) { - authInfoService.save(loginInfo, passwordHasher.hash(credentials.password)) + authInfoRepository.update(loginInfo, passwordHasher.hash(credentials.password)) } loginInfo case Some(hasher) => throw new InvalidPasswordException(InvalidPassword.format(id)) diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/providers/OAuth1Provider.scala b/silhouette/app/com/mohiva/play/silhouette/impl/providers/OAuth1Provider.scala index 4153093b6..c71877ea8 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/providers/OAuth1Provider.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/providers/OAuth1Provider.scala @@ -20,7 +20,6 @@ package com.mohiva.play.silhouette.impl.providers import com.mohiva.play.silhouette.api._ -import com.mohiva.play.silhouette.api.services.AuthInfo import com.mohiva.play.silhouette.api.util.{ ExtractableRequest, HTTPLayer } import com.mohiva.play.silhouette.impl.exceptions.{ AccessDeniedException, UnexpectedResponseException } import com.mohiva.play.silhouette.impl.providers.OAuth1Provider._ diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/providers/OAuth2Provider.scala b/silhouette/app/com/mohiva/play/silhouette/impl/providers/OAuth2Provider.scala index 6f671e622..1ddbdf1d8 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/providers/OAuth2Provider.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/providers/OAuth2Provider.scala @@ -23,7 +23,6 @@ import java.net.URLEncoder._ import com.mohiva.play.silhouette.api._ import com.mohiva.play.silhouette.api.exceptions._ -import com.mohiva.play.silhouette.api.services.AuthInfo import com.mohiva.play.silhouette.api.util.{ ExtractableRequest, HTTPLayer } import com.mohiva.play.silhouette.impl.exceptions.{ AccessDeniedException, UnexpectedResponseException } import com.mohiva.play.silhouette.impl.providers.OAuth2Provider._ diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/providers/OpenIDProvider.scala b/silhouette/app/com/mohiva/play/silhouette/impl/providers/OpenIDProvider.scala index 736e67e14..7fcfa96b7 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/providers/OpenIDProvider.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/providers/OpenIDProvider.scala @@ -18,7 +18,6 @@ package com.mohiva.play.silhouette.impl.providers import java.net.URLEncoder import com.mohiva.play.silhouette.api._ -import com.mohiva.play.silhouette.api.services.AuthInfo import com.mohiva.play.silhouette.api.util.{ ExtractableRequest, HTTPLayer } import com.mohiva.play.silhouette.impl.exceptions.UnexpectedResponseException import com.mohiva.play.silhouette.impl.providers.OpenIDProvider._ diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/providers/SocialProvider.scala b/silhouette/app/com/mohiva/play/silhouette/impl/providers/SocialProvider.scala index bd90321bc..ee8d1362d 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/providers/SocialProvider.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/providers/SocialProvider.scala @@ -16,10 +16,8 @@ package com.mohiva.play.silhouette.impl.providers import java.net.URI - -import com.mohiva.play.silhouette.api.services.AuthInfo import com.mohiva.play.silhouette.api.util.ExtractableRequest -import com.mohiva.play.silhouette.api.{ LoginInfo, Provider } +import com.mohiva.play.silhouette.api.{ AuthInfo, LoginInfo, Provider } import com.mohiva.play.silhouette.impl.exceptions.ProfileRetrievalException import com.mohiva.play.silhouette.impl.providers.SocialProfileBuilder._ import org.apache.commons.lang3.reflect.TypeUtils diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/repositories/DelegableAuthInfoRepository.scala b/silhouette/app/com/mohiva/play/silhouette/impl/repositories/DelegableAuthInfoRepository.scala new file mode 100644 index 000000000..8f9b9bb93 --- /dev/null +++ b/silhouette/app/com/mohiva/play/silhouette/impl/repositories/DelegableAuthInfoRepository.scala @@ -0,0 +1,114 @@ +/** + * Copyright 2015 Mohiva Organisation (license at mohiva dot com) + * + * 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 com.mohiva.play.silhouette.impl.repositories + +import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository +import com.mohiva.play.silhouette.api.{ AuthInfo, LoginInfo } +import com.mohiva.play.silhouette.impl.daos.{ AuthInfoDAO, DelegableAuthInfoDAO } +import com.mohiva.play.silhouette.impl.repositories.DelegableAuthInfoRepository._ +import play.api.libs.concurrent.Execution.Implicits._ + +import scala.concurrent.Future +import scala.reflect.ClassTag + +/** + * An implementation of the auth info repository which delegates the storage of an auth info instance to its + * appropriate DAO. + * + * Due the nature of the different auth information it is hard to persist the data in a single data structure, + * expect the data gets stored in a serialized format. With this implementation it is possible to store the + * different auth info in different backing stores. If we speak of a relational database, then the auth info + * can be stored in different tables. And the tables represents the internal data structure of each auth info + * object. + * + * @param daos The auth info DAO implementations. + */ +class DelegableAuthInfoRepository(daos: DelegableAuthInfoDAO[_]*) extends AuthInfoRepository { + + /** + * Finds the auth info which is linked with the specified login info. + * + * @param loginInfo The linked login info. + * @param tag The class tag of the auth info. + * @tparam T The type of the auth info to handle. + * @return The found auth info or None if no auth info could be found for the given login info. + */ + def find[T <: AuthInfo](loginInfo: LoginInfo)(implicit tag: ClassTag[T]): Future[Option[T]] = { + daos.find(_.classTag == tag) match { + case Some(dao) => dao.find(loginInfo).map(_.map(_.asInstanceOf[T])) + case _ => throw new Exception(FindError.format(tag.runtimeClass)) + } + } + + /** + * Adds new auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be saved. + * @param authInfo The auth info to save. + * @tparam T The type of the auth info to handle. + * @return The saved auth info. + */ + def add[T <: AuthInfo](loginInfo: LoginInfo, authInfo: T): Future[T] = { + daos.find(_.classTag.runtimeClass == authInfo.getClass) match { + case Some(dao) => dao.asInstanceOf[AuthInfoDAO[T]].add(loginInfo, authInfo) + case _ => throw new Exception(AddError.format(authInfo.getClass)) + } + } + + /** + * Updates the auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be updated. + * @param authInfo The auth info to update. + * @tparam T The type of the auth info to handle. + * @return The updated auth info. + */ + def update[T <: AuthInfo](loginInfo: LoginInfo, authInfo: T): Future[T] = { + daos.find(_.classTag.runtimeClass == authInfo.getClass) match { + case Some(dao) => dao.asInstanceOf[AuthInfoDAO[T]].update(loginInfo, authInfo) + case _ => throw new Exception(UpdateError.format(authInfo.getClass)) + } + } + + /** + * Removes the auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be removed. + * @param tag The class tag of the auth info. + * @tparam T The type of the auth info to handle. + * @return A future to wait for the process to be completed. + */ + def remove[T <: AuthInfo](loginInfo: LoginInfo)(implicit tag: ClassTag[T]): Future[Unit] = { + daos.find(_.classTag == tag) match { + case Some(dao) => dao.remove(loginInfo) + case _ => throw new Exception(RemoveError.format(tag.runtimeClass)) + } + } +} + +/** + * The companion object. + */ +object DelegableAuthInfoRepository { + + /** + * The error messages. + */ + val AddError = "Cannot add auth info of type: %s" + val FindError = "Cannot find auth info of type: %s" + val UpdateError = "Cannot update auth info of type: %s" + val RemoveError = "Cannot remove auth info of type: %s" +} diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/repositories/package.scala b/silhouette/app/com/mohiva/play/silhouette/impl/repositories/package.scala new file mode 100644 index 000000000..d563239b3 --- /dev/null +++ b/silhouette/app/com/mohiva/play/silhouette/impl/repositories/package.scala @@ -0,0 +1,21 @@ +/** + * Copyright 2015 Mohiva Organisation (license at mohiva dot com) + * + * 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 com.mohiva.play.silhouette.impl + +/** + * Provides implementations of the repositories. + */ +package object repositories {} diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/services/DelegableAuthInfoService.scala b/silhouette/app/com/mohiva/play/silhouette/impl/services/DelegableAuthInfoService.scala deleted file mode 100644 index d148280ba..000000000 --- a/silhouette/app/com/mohiva/play/silhouette/impl/services/DelegableAuthInfoService.scala +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2015 Mohiva Organisation (license at mohiva dot com) - * - * 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 com.mohiva.play.silhouette.impl.services - -import com.mohiva.play.silhouette.api.LoginInfo -import com.mohiva.play.silhouette.api.services.{ AuthInfo, AuthInfoService } -import com.mohiva.play.silhouette.impl.daos.{ AuthInfoDAO, DelegableAuthInfoDAO } -import com.mohiva.play.silhouette.impl.services.DelegableAuthInfoService._ -import play.api.libs.concurrent.Execution.Implicits._ - -import scala.concurrent.Future -import scala.reflect.ClassTag - -/** - * An implementation of the auth info service which delegates the storage of an auth info instance to its - * appropriate DAO. - * - * Due the nature of the different auth information it is hard to persist the data in a single data structure, - * expect the data gets stored in a serialized format. With this implementation it is possible to store the - * different auth info in different backing stores. If we speak of a relational database, then the auth info - * can be stored in different tables. And the tables represents the internal data structure of each auth info - * object. - * - * @param daos The auth info DAO implementations. - */ -class DelegableAuthInfoService(daos: DelegableAuthInfoDAO[_]*) extends AuthInfoService { - - /** - * Saves auth info. - * - * This method gets called when a user logs in (social auth) or registers. This is the chance - * to persist the auth info for a provider in the backing store. If the application supports - * the concept of "merged identities", i.e., the same user being able to authenticate through - * different providers, then make sure that the auth info for every linked login info gets - * stored separately. - * - * @param loginInfo The login info for which the auth info should be saved. - * @param authInfo The auth info to save. - * @return The saved auth info. - */ - def save[T <: AuthInfo](loginInfo: LoginInfo, authInfo: T): Future[T] = { - daos.find(_.classTag.runtimeClass == authInfo.getClass) match { - case Some(dao) => dao.asInstanceOf[AuthInfoDAO[T]].save(loginInfo, authInfo) - case _ => throw new Exception(SaveError.format(authInfo.getClass)) - } - } - - /** - * Retrieves the auth info which is linked with the specified login info. - * - * @param loginInfo The linked login info. - * @param tag The class tag of the auth info. - * @return The retrieved auth info or None if no auth info could be retrieved for the given login info. - */ - def retrieve[T <: AuthInfo](loginInfo: LoginInfo)(implicit tag: ClassTag[T]): Future[Option[T]] = { - daos.find(_.classTag == tag) match { - case Some(dao) => dao.find(loginInfo).map(_.map(_.asInstanceOf[T])) - case _ => throw new Exception(RetrieveError.format(tag.runtimeClass)) - } - } -} - -/** - * The companion object. - */ -object DelegableAuthInfoService { - - /** - * The error messages. - */ - val SaveError = "Cannot save auth info of type: %s" - val RetrieveError = "Cannot search for auth info of type: %s" -} diff --git a/silhouette/app/com/mohiva/play/silhouette/impl/util/PlayCacheLayer.scala b/silhouette/app/com/mohiva/play/silhouette/impl/util/PlayCacheLayer.scala index 29573bad0..bb6e5b4d9 100644 --- a/silhouette/app/com/mohiva/play/silhouette/impl/util/PlayCacheLayer.scala +++ b/silhouette/app/com/mohiva/play/silhouette/impl/util/PlayCacheLayer.scala @@ -15,18 +15,21 @@ */ package com.mohiva.play.silhouette.impl.util +import javax.inject.Inject + import com.mohiva.play.silhouette.api.util.CacheLayer -import play.api.Play.current -import play.api.cache.Cache -import play.api.libs.concurrent.Execution.Implicits._ +import play.api.cache.CacheApi import scala.concurrent.Future +import scala.concurrent.duration.Duration import scala.reflect.ClassTag /** * Implementation of the cache layer which uses the default Play cache plugin. + * + * @param cacheApi Plays cache API implementation. */ -class PlayCacheLayer extends CacheLayer { +class PlayCacheLayer @Inject() (cacheApi: CacheApi) extends CacheLayer { /** * Save a value in cache. @@ -36,9 +39,9 @@ class PlayCacheLayer extends CacheLayer { * @param expiration Expiration time in seconds (0 second means eternity). * @return The value saved in cache. */ - def save[T](key: String, value: T, expiration: Int = 0): Future[T] = { - Cache.set(key, value, expiration) - Future.successful(value) + def save[T](key: String, value: T, expiration: Duration = Duration.Inf): Future[T] = Future.successful { + cacheApi.set(key, value, expiration) + value } /** @@ -48,7 +51,7 @@ class PlayCacheLayer extends CacheLayer { * @tparam T The type of the object to return. * @return The found value or None if no value could be found. */ - def find[T: ClassTag](key: String): Future[Option[T]] = Future(Cache.getAs[T](key)) + def find[T: ClassTag](key: String): Future[Option[T]] = Future.successful(cacheApi.get[T](key)) /** * Remove a value from the cache. @@ -56,5 +59,5 @@ class PlayCacheLayer extends CacheLayer { * @param key Item key. * @return An empty future to wait for removal. */ - def remove(key: String) = Future(Cache.remove(key)) + def remove(key: String) = Future.successful(cacheApi.remove(key)) } diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/BearerTokenAuthenticatorSpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/BearerTokenAuthenticatorSpec.scala index e2d2d1e0a..e9aa61db7 100644 --- a/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/BearerTokenAuthenticatorSpec.scala +++ b/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/BearerTokenAuthenticatorSpec.scala @@ -145,17 +145,17 @@ class BearerTokenAuthenticatorSpec extends PlaySpecification with Mockito { "The `init` method of the service" should { "save the authenticator in backing store" in new Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[BearerTokenAuthenticator]) } + dao.add(any) answers { p => Future.successful(p.asInstanceOf[BearerTokenAuthenticator]) } implicit val request = FakeRequest() val token = await(service.init(authenticator)) token must be equalTo authenticator.id - there was one(dao).save(authenticator) + there was one(dao).add(authenticator) } "throws an AuthenticatorInitializationException exception if an error occurred during initialization" in new Context { - dao.save(any) returns Future.failed(new Exception("Cannot store authenticator")) + dao.add(any) returns Future.failed(new Exception("Cannot store authenticator")) implicit val request = FakeRequest() @@ -224,17 +224,17 @@ class BearerTokenAuthenticatorSpec extends PlaySpecification with Mockito { "The `update` method of the service" should { "update the authenticator in backing store" in new Context { - dao.save(any) returns Future.successful(authenticator) + dao.update(any) returns Future.successful(authenticator) implicit val request = FakeRequest() await(service.update(authenticator, Results.Ok)) - there was one(dao).save(authenticator) + there was one(dao).update(authenticator) } "return the result if the authenticator could be stored in backing store" in new Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[BearerTokenAuthenticator]) } + dao.update(any) answers { p => Future.successful(p.asInstanceOf[BearerTokenAuthenticator]) } implicit val request = FakeRequest() val result = service.update(authenticator, Results.Ok) @@ -243,7 +243,7 @@ class BearerTokenAuthenticatorSpec extends PlaySpecification with Mockito { } "throws an AuthenticatorUpdateException exception if an error occurred during update" in new Context { - dao.save(any) returns Future.failed(new Exception("Cannot store authenticator")) + dao.update(any) returns Future.failed(new Exception("Cannot store authenticator")) implicit val request = FakeRequest() @@ -261,7 +261,7 @@ class BearerTokenAuthenticatorSpec extends PlaySpecification with Mockito { val id = "new-test-id" dao.remove(authenticator.id) returns Future.successful(()) - dao.save(any) answers { p => Future.successful(p.asInstanceOf[BearerTokenAuthenticator]) } + dao.add(any) answers { p => Future.successful(p.asInstanceOf[BearerTokenAuthenticator]) } idGenerator.generate returns Future.successful(id) clock.now returns now @@ -276,7 +276,7 @@ class BearerTokenAuthenticatorSpec extends PlaySpecification with Mockito { val id = "new-test-id" dao.remove(any) returns Future.successful(()) - dao.save(any) answers { p => Future.successful(p.asInstanceOf[BearerTokenAuthenticator]) } + dao.add(any) answers { p => Future.successful(p.asInstanceOf[BearerTokenAuthenticator]) } idGenerator.generate returns Future.successful(id) clock.now returns now @@ -291,7 +291,7 @@ class BearerTokenAuthenticatorSpec extends PlaySpecification with Mockito { val id = "new-test-id" dao.remove(any) returns Future.successful(()) - dao.save(any) returns Future.failed(new Exception("Cannot store authenticator")) + dao.add(any) returns Future.failed(new Exception("Cannot store authenticator")) idGenerator.generate returns Future.successful(id) clock.now returns now diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/CookieAuthenticatorSpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/CookieAuthenticatorSpec.scala index 59b67acbd..8f0f10b32 100644 --- a/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/CookieAuthenticatorSpec.scala +++ b/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/CookieAuthenticatorSpec.scala @@ -192,16 +192,16 @@ class CookieAuthenticatorSpec extends PlaySpecification with Mockito { "The `init` method of the service" should { "return a cookie if authenticator could be saved in backing store" in new Context { - dao.save(any) returns Future.successful(authenticator) + dao.add(any) returns Future.successful(authenticator) implicit val request = FakeRequest() await(service.init(authenticator)) must be equalTo cookie - there was one(dao).save(any) + there was one(dao).add(any) } "throws an AuthenticatorInitializationException exception if an error occurred during initialization" in new Context { - dao.save(any) returns Future.failed(new Exception("Cannot store authenticator")) + dao.add(any) returns Future.failed(new Exception("Cannot store authenticator")) implicit val request = FakeRequest() @@ -214,8 +214,6 @@ class CookieAuthenticatorSpec extends PlaySpecification with Mockito { "The result `embed` method of the service" should { "return the response with a cookie" in new Context { - dao.save(any) returns Future.successful(authenticator) - implicit val request = FakeRequest() val result = service.embed(cookie, Results.Ok) @@ -225,24 +223,18 @@ class CookieAuthenticatorSpec extends PlaySpecification with Mockito { "The request `embed` method of the service" should { "return the request with a cookie" in new Context { - dao.save(any) returns Future.successful(authenticator) - val request = service.embed(cookie, FakeRequest()) request.cookies.get(settings.cookieName) should beSome[Cookie].which(cookieMatcher) } "override an existing cookie" in new Context { - dao.save(any) returns Future.successful(authenticator) - val request = service.embed(cookie, FakeRequest().withCookies(Cookie(settings.cookieName, "test"))) request.cookies.get(settings.cookieName) should beSome[Cookie].which(cookieMatcher) } "keep non authenticator related cookies" in new Context { - dao.save(any) returns Future.successful(authenticator) - val request = service.embed(cookie, FakeRequest().withCookies(Cookie("test", "test"))) request.cookies.get(settings.cookieName) should beSome[Cookie].which(cookieMatcher) @@ -277,17 +269,17 @@ class CookieAuthenticatorSpec extends PlaySpecification with Mockito { "The `update` method of the service" should { "update the authenticator in backing store" in new Context { - dao.save(any) returns Future.successful(authenticator) + dao.update(any) returns Future.successful(authenticator) implicit val request = FakeRequest() await(service.update(authenticator, Results.Ok)) - there was one(dao).save(authenticator) + there was one(dao).update(authenticator) } "return the result if the authenticator could be stored in backing store" in new Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[CookieAuthenticator]) } + dao.update(any) answers { p => Future.successful(p.asInstanceOf[CookieAuthenticator]) } implicit val request = FakeRequest() val result = service.update(authenticator, Results.Ok) @@ -296,7 +288,7 @@ class CookieAuthenticatorSpec extends PlaySpecification with Mockito { } "throws an AuthenticatorUpdateException exception if an error occurred during update" in new Context { - dao.save(any) returns Future.failed(new Exception("Cannot store authenticator")) + dao.update(any) returns Future.failed(new Exception("Cannot store authenticator")) implicit val request = FakeRequest() @@ -314,7 +306,7 @@ class CookieAuthenticatorSpec extends PlaySpecification with Mockito { val id = "new-test-id" dao.remove(authenticator.id) returns Future.successful(()) - dao.save(any) answers { p => Future.successful(p.asInstanceOf[CookieAuthenticator]) } + dao.add(any) answers { p => Future.successful(p.asInstanceOf[CookieAuthenticator]) } idGenerator.generate returns Future.successful(id) clock.now returns now @@ -329,7 +321,7 @@ class CookieAuthenticatorSpec extends PlaySpecification with Mockito { val id = "new-test-id" dao.remove(any) returns Future.successful(()) - dao.save(any) answers { p => Future.successful(p.asInstanceOf[CookieAuthenticator]) } + dao.add(any) answers { p => Future.successful(p.asInstanceOf[CookieAuthenticator]) } idGenerator.generate returns Future.successful(id) clock.now returns now @@ -345,7 +337,7 @@ class CookieAuthenticatorSpec extends PlaySpecification with Mockito { c.secure must be equalTo settings.secureCookie c.httpOnly must be equalTo settings.httpOnlyCookie } - there was one(dao).save(any) + there was one(dao).add(any) } "throws an AuthenticatorRenewalException exception if an error occurred during renewal" in new Context { @@ -354,7 +346,7 @@ class CookieAuthenticatorSpec extends PlaySpecification with Mockito { val id = "new-test-id" dao.remove(any) returns Future.successful(()) - dao.save(any) returns Future.failed(new Exception("Cannot store authenticator")) + dao.add(any) returns Future.failed(new Exception("Cannot store authenticator")) idGenerator.generate returns Future.successful(id) clock.now returns now diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/JWTAuthenticatorSpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/JWTAuthenticatorSpec.scala index 706983fa7..c38deb985 100644 --- a/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/JWTAuthenticatorSpec.scala +++ b/silhouette/test/com/mohiva/play/silhouette/impl/authenticators/JWTAuthenticatorSpec.scala @@ -305,13 +305,13 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch "The `init` method of the service" should { "return the token if DAO is enabled and authenticator could be saved in backing store" in new WithApplication with Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } + dao.add(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } implicit val request = FakeRequest() val token = await(service(Some(dao)).init(authenticator)) unserialize(token)(settings).get must be equalTo authenticator - there was one(dao).save(any) + there was one(dao).add(any) } "return the token if DAO is disabled" in new WithApplication with Context { @@ -320,11 +320,11 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch val token = await(service(None).init(authenticator)) unserialize(token)(settings).get must be equalTo authenticator - there was no(dao).save(any) + there was no(dao).add(any) } "throws an AuthenticatorInitializationException exception if an error occurred during initialization" in new Context { - dao.save(any) returns Future.failed(new Exception("Cannot store authenticator")) + dao.add(any) returns Future.failed(new Exception("Cannot store authenticator")) implicit val request = FakeRequest() val okResult = Future.successful(Results.Ok) @@ -338,7 +338,6 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch "The result `embed` method of the service" should { "return the response with a header" in new WithApplication with Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } implicit val request = FakeRequest() val token = serialize(authenticator)(settings) @@ -350,8 +349,6 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch "The request `embed` method of the service" should { "return the request with a header " in new WithApplication with Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } - val token = serialize(authenticator)(settings) val request = service(Some(dao)).embed(token, FakeRequest()) @@ -359,8 +356,6 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch } "override an existing token" in new WithApplication with Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } - val token = serialize(authenticator)(settings) val request = service(Some(dao)).embed(token, FakeRequest().withHeaders(settings.headerName -> "test")) @@ -368,8 +363,6 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch } "keep non authenticator related headers" in new WithApplication with Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } - val token = serialize(authenticator)(settings) val request = service(Some(dao)).embed(token, FakeRequest().withHeaders("test" -> "test")) @@ -402,39 +395,39 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch "The `update` method of the service" should { "update the authenticator in backing store" in new WithApplication with Context { - dao.save(any) returns Future.successful(authenticator) + dao.update(any) returns Future.successful(authenticator) implicit val request = FakeRequest() await(service(Some(dao)).update(authenticator, Results.Ok)) - there was one(dao).save(authenticator) + there was one(dao).update(authenticator) } "return the result if the authenticator could be stored in backing store" in new WithApplication with Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } + dao.update(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } implicit val request = FakeRequest() val result = service(Some(dao)).update(authenticator, Results.Ok) status(result) must be equalTo OK unserialize(header(settings.headerName, result).get)(settings).get must be equalTo authenticator - there was one(dao).save(authenticator) + there was one(dao).update(authenticator) } "return the result if backing store is disabled" in new WithApplication with Context { - dao.save(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } + dao.update(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } implicit val request = FakeRequest() val result = service(None).update(authenticator, Results.Ok) status(result) must be equalTo OK unserialize(header(settings.headerName, result).get)(settings).get must be equalTo authenticator - there was no(dao).save(any) + there was no(dao).update(any) } "throws an AuthenticatorUpdateException exception if an error occurred during update" in new WithApplication with Context { - dao.save(any) returns Future.failed(new Exception("Cannot store authenticator")) + dao.update(any) returns Future.failed(new Exception("Cannot store authenticator")) implicit val request = FakeRequest() @@ -452,7 +445,7 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch val id = "new-test-id" dao.remove(any) returns Future.successful(()) - dao.save(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } + dao.add(any) answers { p => Future.successful(p.asInstanceOf[JWTAuthenticator]) } idGenerator.generate returns Future.successful(id) clock.now returns now @@ -463,6 +456,8 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch expirationDate = clock.now.plusSeconds(settings.authenticatorExpiry), lastUsedDate = clock.now ) + + there was one(dao).add(any) there was one(dao).remove(authenticator.id) } @@ -482,7 +477,7 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch lastUsedDate = clock.now ) there was no(dao).remove(any) - there was no(dao).save(any) + there was no(dao).add(any) } "throws an AuthenticatorRenewalException exception if an error occurred during renewal" in new Context { @@ -491,7 +486,7 @@ class JWTAuthenticatorSpec extends PlaySpecification with Mockito with JsonMatch val id = "new-test-id" dao.remove(any) returns Future.successful(()) - dao.save(any) returns Future.failed(new Exception("Cannot store authenticator")) + dao.add(any) returns Future.failed(new Exception("Cannot store authenticator")) idGenerator.generate returns Future.successful(id) clock.now returns now diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/daos/CacheAuthenticatorDAOSpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/daos/CacheAuthenticatorDAOSpec.scala index 943ce43c1..1f2409328 100644 --- a/silhouette/test/com/mohiva/play/silhouette/impl/daos/CacheAuthenticatorDAOSpec.scala +++ b/silhouette/test/com/mohiva/play/silhouette/impl/daos/CacheAuthenticatorDAOSpec.scala @@ -19,34 +19,25 @@ import com.mohiva.play.silhouette.api.StorableAuthenticator import com.mohiva.play.silhouette.api.util.CacheLayer import org.specs2.mock.Mockito import org.specs2.specification.Scope -import play.api.test.{ PlaySpecification, WithApplication } +import play.api.test.PlaySpecification import scala.concurrent.Future +import scala.concurrent.duration.Duration /** * Test case for the [[com.mohiva.play.silhouette.impl.daos.CacheAuthenticatorDAO]] class. */ class CacheAuthenticatorDAOSpec extends PlaySpecification with Mockito { - "The `save` method" should { - "save value in cache" in new WithApplication with Context { - authenticator.id returns "test-id" - cacheLayer.save("test-id", authenticator, 0) returns Future.successful(authenticator) - - await(dao.save(authenticator)) must be equalTo authenticator - there was one(cacheLayer).save("test-id", authenticator, 0) - } - } - "The `find` method" should { - "return value from cache" in new WithApplication with Context { + "return value from cache" in new Context { cacheLayer.find[StorableAuthenticator]("test-id") returns Future.successful(Some(authenticator)) await(dao.find("test-id")) must beSome(authenticator) there was one(cacheLayer).find[StorableAuthenticator]("test-id") } - "return None if value couldn't be found in cache" in new WithApplication with Context { + "return None if value couldn't be found in cache" in new Context { cacheLayer.find[StorableAuthenticator]("test-id") returns Future.successful(None) await(dao.find("test-id")) must beNone @@ -54,8 +45,28 @@ class CacheAuthenticatorDAOSpec extends PlaySpecification with Mockito { } } + "The `add` method" should { + "add value in cache" in new Context { + authenticator.id returns "test-id" + cacheLayer.save("test-id", authenticator, Duration.Inf) returns Future.successful(authenticator) + + await(dao.add(authenticator)) must be equalTo authenticator + there was one(cacheLayer).save("test-id", authenticator, Duration.Inf) + } + } + + "The `update` method" should { + "update value in cache" in new Context { + authenticator.id returns "test-id" + cacheLayer.save("test-id", authenticator, Duration.Inf) returns Future.successful(authenticator) + + await(dao.update(authenticator)) must be equalTo authenticator + there was one(cacheLayer).save("test-id", authenticator, Duration.Inf) + } + } + "The `remove` method" should { - "removes value from cache" in new WithApplication with Context { + "remove value from cache" in new Context { cacheLayer.remove("test-id") returns Future.successful(()) await(dao.remove("test-id")) must be equalTo (()) diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/providers/BasicAuthProviderSpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/providers/BasicAuthProviderSpec.scala index 270579afa..61d129bbf 100644 --- a/silhouette/test/com/mohiva/play/silhouette/impl/providers/BasicAuthProviderSpec.scala +++ b/silhouette/test/com/mohiva/play/silhouette/impl/providers/BasicAuthProviderSpec.scala @@ -17,7 +17,7 @@ package com.mohiva.play.silhouette.impl.providers import com.mohiva.play.silhouette.api.LoginInfo import com.mohiva.play.silhouette.api.exceptions._ -import com.mohiva.play.silhouette.api.services.AuthInfoService +import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository import com.mohiva.play.silhouette.api.util.{ Base64, Credentials, PasswordHasher, PasswordInfo } import com.mohiva.play.silhouette.impl.exceptions.{ InvalidPasswordException, IdentityNotFoundException } import com.mohiva.play.silhouette.impl.providers.BasicAuthProvider._ @@ -37,7 +37,7 @@ class BasicAuthProviderSpec extends PlaySpecification with Mockito { val loginInfo = new LoginInfo(provider.id, credentials.identifier) val request = FakeRequest().withHeaders(AUTHORIZATION -> encodeCredentials(credentials)) - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(None) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(None) await(provider.authenticate(request)) must throwA[IdentityNotFoundException].like { case e => e.getMessage must beEqualTo(UnknownCredentials.format(provider.id)) @@ -50,7 +50,7 @@ class BasicAuthProviderSpec extends PlaySpecification with Mockito { val request = FakeRequest().withHeaders(AUTHORIZATION -> encodeCredentials(credentials)) fooHasher.matches(passwordInfo, credentials.password) returns false - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) await(provider.authenticate(request)) must throwA[InvalidPasswordException].like { case e => e.getMessage must beEqualTo(InvalidPassword.format(provider.id)) @@ -62,7 +62,7 @@ class BasicAuthProviderSpec extends PlaySpecification with Mockito { val loginInfo = LoginInfo(provider.id, credentials.identifier) val request = FakeRequest().withHeaders(AUTHORIZATION -> encodeCredentials(credentials)) - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) await(provider.authenticate(request)) must throwA[ConfigurationException].like { case e => e.getMessage must beEqualTo(UnsupportedHasher.format(provider.id, "unknown", "foo, bar")) @@ -85,7 +85,7 @@ class BasicAuthProviderSpec extends PlaySpecification with Mockito { val request = FakeRequest().withHeaders(AUTHORIZATION -> encodeCredentials(credentials)) fooHasher.matches(passwordInfo, credentials.password) returns true - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) await(provider.authenticate(request)) must beSome(loginInfo) } @@ -97,10 +97,10 @@ class BasicAuthProviderSpec extends PlaySpecification with Mockito { fooHasher.hash(credentials.password) returns passwordInfo barHasher.matches(passwordInfo, credentials.password) returns true - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) await(provider.authenticate(request)) must beSome(loginInfo) - there was one(authInfoService).save(loginInfo, passwordInfo) + there was one(authInfoRepository).update(loginInfo, passwordInfo) } } @@ -133,14 +133,14 @@ class BasicAuthProviderSpec extends PlaySpecification with Mockito { } /** - * The auth info service mock. + * The auth info repository mock. */ - lazy val authInfoService = mock[AuthInfoService] + lazy val authInfoRepository = mock[AuthInfoRepository] /** * The provider to test. */ - lazy val provider = new BasicAuthProvider(authInfoService, fooHasher, List(fooHasher, barHasher)) + lazy val provider = new BasicAuthProvider(authInfoRepository, fooHasher, List(fooHasher, barHasher)) /** * Creates the credentials to send within the header. diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/providers/CredentialsProviderSpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/providers/CredentialsProviderSpec.scala index f9963c11c..fc4b5d76e 100644 --- a/silhouette/test/com/mohiva/play/silhouette/impl/providers/CredentialsProviderSpec.scala +++ b/silhouette/test/com/mohiva/play/silhouette/impl/providers/CredentialsProviderSpec.scala @@ -17,7 +17,7 @@ package com.mohiva.play.silhouette.impl.providers import com.mohiva.play.silhouette.api.LoginInfo import com.mohiva.play.silhouette.api.exceptions._ -import com.mohiva.play.silhouette.api.services.AuthInfoService +import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository import com.mohiva.play.silhouette.api.util.{ Credentials, PasswordHasher, PasswordInfo } import com.mohiva.play.silhouette.impl.exceptions.{ IdentityNotFoundException, InvalidPasswordException } import com.mohiva.play.silhouette.impl.providers.CredentialsProvider._ @@ -36,7 +36,7 @@ class CredentialsProviderSpec extends PlaySpecification with Mockito { "throw IdentityNotFoundException if no auth info could be found for the given credentials" in new WithApplication with Context { val loginInfo = new LoginInfo(provider.id, credentials.identifier) - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(None) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(None) await(provider.authenticate(credentials)) must throwA[IdentityNotFoundException].like { case e => e.getMessage must beEqualTo(UnknownCredentials.format(provider.id)) @@ -48,7 +48,7 @@ class CredentialsProviderSpec extends PlaySpecification with Mockito { val loginInfo = LoginInfo(provider.id, credentials.identifier) fooHasher.matches(passwordInfo, credentials.password) returns false - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) await(provider.authenticate(credentials)) must throwA[InvalidPasswordException].like { case e => e.getMessage must beEqualTo(InvalidPassword.format(provider.id)) @@ -59,7 +59,7 @@ class CredentialsProviderSpec extends PlaySpecification with Mockito { val passwordInfo = PasswordInfo("unknown", "hashed(s3cr3t)") val loginInfo = LoginInfo(provider.id, credentials.identifier) - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) await(provider.authenticate(credentials)) must throwA[ConfigurationException].like { case e => e.getMessage must beEqualTo(UnsupportedHasher.format(provider.id, "unknown", "foo, bar")) @@ -71,7 +71,7 @@ class CredentialsProviderSpec extends PlaySpecification with Mockito { val loginInfo = LoginInfo(provider.id, credentials.identifier) fooHasher.matches(passwordInfo, credentials.password) returns true - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) await(provider.authenticate(credentials)) must be equalTo loginInfo } @@ -82,10 +82,10 @@ class CredentialsProviderSpec extends PlaySpecification with Mockito { fooHasher.hash(credentials.password) returns passwordInfo barHasher.matches(passwordInfo, credentials.password) returns true - authInfoService.retrieve[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) + authInfoRepository.find[PasswordInfo](loginInfo) returns Future.successful(Some(passwordInfo)) await(provider.authenticate(credentials)) must be equalTo loginInfo - there was one(authInfoService).save(loginInfo, passwordInfo) + there was one(authInfoRepository).update(loginInfo, passwordInfo) } } @@ -118,13 +118,13 @@ class CredentialsProviderSpec extends PlaySpecification with Mockito { } /** - * The auth info service mock. + * The auth info repository mock. */ - lazy val authInfoService = mock[AuthInfoService] + lazy val authInfoRepository = mock[AuthInfoRepository] /** * The provider to test. */ - lazy val provider = new CredentialsProvider(authInfoService, fooHasher, List(fooHasher, barHasher)) + lazy val provider = new CredentialsProvider(authInfoRepository, fooHasher, List(fooHasher, barHasher)) } } diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/providers/SocialProviderSpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/providers/SocialProviderSpec.scala index d105efc8d..64998dde6 100644 --- a/silhouette/test/com/mohiva/play/silhouette/impl/providers/SocialProviderSpec.scala +++ b/silhouette/test/com/mohiva/play/silhouette/impl/providers/SocialProviderSpec.scala @@ -15,7 +15,7 @@ */ package com.mohiva.play.silhouette.impl.providers -import com.mohiva.play.silhouette.api.services.AuthInfo +import com.mohiva.play.silhouette.api.AuthInfo import org.specs2.matcher.{ JsonMatchers, MatchResult } import org.specs2.mock.Mockito import play.api.mvc.Result diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/repositories/DelegableAuthInfoRepositorySpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/repositories/DelegableAuthInfoRepositorySpec.scala new file mode 100644 index 000000000..94065495a --- /dev/null +++ b/silhouette/test/com/mohiva/play/silhouette/impl/repositories/DelegableAuthInfoRepositorySpec.scala @@ -0,0 +1,316 @@ +/** + * Copyright 2015 Mohiva Organisation (license at mohiva dot com) + * + * 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 com.mohiva.play.silhouette.impl.repositories + +import com.google.inject.{ AbstractModule, Guice, Provides } +import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository +import com.mohiva.play.silhouette.api.util.PasswordInfo +import com.mohiva.play.silhouette.api.{ AuthInfo, LoginInfo } +import com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO +import com.mohiva.play.silhouette.impl.providers.{ OAuth1Info, OAuth2Info } +import com.mohiva.play.silhouette.impl.repositories.DelegableAuthInfoRepository._ +import net.codingwell.scalaguice.ScalaModule +import org.specs2.mock.Mockito +import org.specs2.specification.Scope +import play.api.test.PlaySpecification + +import scala.collection.mutable +import scala.concurrent.Future +import scala.reflect.ClassTag + +/** + * Test case for the [[DelegableAuthInfoRepository]] trait. + */ +class DelegableAuthInfoRepositorySpec extends PlaySpecification with Mockito { + + "The `find` method" should { + "delegate the PasswordInfo to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(passwordInfoDAO.add(loginInfo, passwordInfo)) + + await(service.find[PasswordInfo](loginInfo)) must beSome(passwordInfo) + there was one(passwordInfoDAO).find(loginInfo) + } + + "delegate the OAuth1Info to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(oauth1InfoDAO.add(loginInfo, oauth1Info)) + + await(service.find[OAuth1Info](loginInfo)) must beSome(oauth1Info) + there was one(oauth1InfoDAO).find(loginInfo) + } + + "delegate the OAuth2Info to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(oauth2InfoDAO.add(loginInfo, oauth2Info)) + + await(service.find[OAuth2Info](loginInfo)) must beSome(oauth2Info) + there was one(oauth2InfoDAO).find(loginInfo) + } + + "throw an Exception if an unsupported type was given" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.find[UnsupportedInfo](loginInfo)) must throwA[Exception].like { + case e => e.getMessage must startWith(FindError.format("")) + } + } + } + + "The `add` method" should { + "delegate the PasswordInfo to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.add(loginInfo, passwordInfo)) must be equalTo passwordInfo + there was one(passwordInfoDAO).add(loginInfo, passwordInfo) + } + + "delegate the OAuth1Info to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.add(loginInfo, oauth1Info)) must be equalTo oauth1Info + there was one(oauth1InfoDAO).add(loginInfo, oauth1Info) + } + + "delegate the OAuth2Info to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.add(loginInfo, oauth2Info)) must be equalTo oauth2Info + there was one(oauth2InfoDAO).add(loginInfo, oauth2Info) + } + + "throw an Exception if an unsupported type was given" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.add(loginInfo, new UnsupportedInfo)) must throwA[Exception].like { + case e => e.getMessage must startWith(AddError.format("")) + } + } + } + + "The `update` method" should { + "delegate the PasswordInfo to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.update(loginInfo, passwordInfo)) must be equalTo passwordInfo + there was one(passwordInfoDAO).update(loginInfo, passwordInfo) + } + + "delegate the OAuth1Info to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.update(loginInfo, oauth1Info)) must be equalTo oauth1Info + there was one(oauth1InfoDAO).update(loginInfo, oauth1Info) + } + + "delegate the OAuth2Info to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.update(loginInfo, oauth2Info)) must be equalTo oauth2Info + there was one(oauth2InfoDAO).update(loginInfo, oauth2Info) + } + + "throw an Exception if an unsupported type was given" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.update(loginInfo, new UnsupportedInfo)) must throwA[Exception].like { + case e => e.getMessage must startWith(UpdateError.format("")) + } + } + } + + "The `remove` method" should { + "delegate the PasswordInfo to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(passwordInfoDAO.add(loginInfo, passwordInfo)) + + await(service.remove[PasswordInfo](loginInfo)) must be equalTo (()) + there was one(passwordInfoDAO).remove(loginInfo) + } + + "delegate the OAuth1Info to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(oauth1InfoDAO.add(loginInfo, oauth1Info)) + + await(service.remove[OAuth1Info](loginInfo)) must be equalTo (()) + there was one(oauth1InfoDAO).remove(loginInfo) + } + + "delegate the OAuth2Info to the correct DAO" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(oauth2InfoDAO.add(loginInfo, oauth2Info)) + + await(service.remove[OAuth2Info](loginInfo)) must be equalTo (()) + there was one(oauth2InfoDAO).remove(loginInfo) + } + + "throw an Exception if an unsupported type was given" in new Context { + val loginInfo = LoginInfo("credentials", "1") + + await(service.remove[UnsupportedInfo](loginInfo)) must throwA[Exception].like { + case e => e.getMessage must startWith(RemoveError.format("")) + } + } + } + + /** + * The context. + */ + trait Context extends Scope { + + /** + * The Guice injector. + */ + val injector = Guice.createInjector(new BaseModule) + + /** + * The auth info. + */ + val passwordInfo = PasswordInfo("test.hasher", "test.password") + val oauth1Info = OAuth1Info("test.token", "test.secret") + val oauth2Info = OAuth2Info("test.token") + + /** + * The DAOs. + */ + lazy val passwordInfoDAO = spy(new PasswordInfoDAO) + lazy val oauth1InfoDAO = spy(new OAuth1InfoDAO) + lazy val oauth2InfoDAO = spy(new OAuth2InfoDAO) + + /** + * The cache instance to store the different auth information instances. + */ + val service = injector.getInstance(classOf[AuthInfoRepository]) + + /** + * An unsupported auth info. + */ + class UnsupportedInfo extends AuthInfo + + /** + * The Guice module. + * + * This is to test if the [[com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO]] can be used for + * dependency injection because it depends on an implicit [[scala.reflect.ClassTag]] parameter which the + * compiler injects at compile time. + */ + class BaseModule extends AbstractModule with ScalaModule { + + /** + * Configures the module. + */ + def configure() { + bind[DelegableAuthInfoDAO[PasswordInfo]].toInstance(passwordInfoDAO) + bind[DelegableAuthInfoDAO[OAuth1Info]].toInstance(oauth1InfoDAO) + bind[DelegableAuthInfoDAO[OAuth2Info]].toInstance(oauth2InfoDAO) + } + + /** + * Provides the auth info repository. + * + * @param passwordInfoDAO The implementation of the delegable password auth info DAO. + * @param oauth1InfoDAO The implementation of the delegable OAuth1 auth info DAO. + * @param oauth2InfoDAO The implementation of the delegable OAuth2 auth info DAO. + * @return The auth info repository instance. + */ + @Provides + def provideAuthInfoService( + passwordInfoDAO: DelegableAuthInfoDAO[PasswordInfo], + oauth1InfoDAO: DelegableAuthInfoDAO[OAuth1Info], + oauth2InfoDAO: DelegableAuthInfoDAO[OAuth2Info]): AuthInfoRepository = { + + new DelegableAuthInfoRepository(passwordInfoDAO, oauth1InfoDAO, oauth2InfoDAO) + } + } + + /** + * The DAO to store the password information. + */ + class PasswordInfoDAO extends InMemoryAuthInfoDAO[PasswordInfo] + + /** + * The DAO to store the OAuth1 information. + */ + class OAuth1InfoDAO extends InMemoryAuthInfoDAO[OAuth1Info] + + /** + * The DAO to store the OAuth2 information. + */ + class OAuth2InfoDAO extends InMemoryAuthInfoDAO[OAuth2Info] + + /** + * An abstract in-memory test helper. + */ + abstract class InMemoryAuthInfoDAO[T <: AuthInfo: ClassTag] extends DelegableAuthInfoDAO[T] { + + /** + * The data store for the OAuth1 info. + */ + var data: mutable.HashMap[LoginInfo, T] = mutable.HashMap() + + /** + * Finds the OAuth1 info which is linked with the specified login info. + * + * @param loginInfo The linked login info. + * @return The retrieved OAuth1 info or None if no OAuth1 info could be retrieved for the given login info. + */ + def find(loginInfo: LoginInfo): Future[Option[T]] = { + Future.successful(Option(data.apply(loginInfo))) + } + + /** + * Adds new auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be added. + * @param authInfo The auth info to add. + * @return The added auth info. + */ + def add(loginInfo: LoginInfo, authInfo: T): Future[T] = { + data += (loginInfo -> authInfo) + Future.successful(authInfo) + } + + /** + * Updates the auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be updated. + * @param authInfo The auth info to update. + * @return The updated auth info. + */ + def update(loginInfo: LoginInfo, authInfo: T): Future[T] = { + data += (loginInfo -> authInfo) + Future.successful(authInfo) + } + + /** + * Removes the auth info for the given login info. + * + * @param loginInfo The login info for which the auth info should be removed. + * @return A future to wait for the process to be completed. + */ + def remove(loginInfo: LoginInfo): Future[Unit] = { + data -= loginInfo + Future.successful(()) + } + } + } +} diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/services/DelegableAuthInfoServiceSpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/services/DelegableAuthInfoServiceSpec.scala deleted file mode 100644 index e047e1a6f..000000000 --- a/silhouette/test/com/mohiva/play/silhouette/impl/services/DelegableAuthInfoServiceSpec.scala +++ /dev/null @@ -1,238 +0,0 @@ -/** - * Copyright 2015 Mohiva Organisation (license at mohiva dot com) - * - * 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 com.mohiva.play.silhouette.impl.services - -import com.google.inject.{ AbstractModule, Guice, Provides } -import com.mohiva.play.silhouette.api.LoginInfo -import com.mohiva.play.silhouette.api.services.{ AuthInfo, AuthInfoService } -import com.mohiva.play.silhouette.api.util.PasswordInfo -import com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO -import com.mohiva.play.silhouette.impl.providers.{ OAuth1Info, OAuth2Info } -import com.mohiva.play.silhouette.impl.services.DelegableAuthInfoService._ -import net.codingwell.scalaguice.ScalaModule -import org.specs2.specification.Scope -import play.api.test.PlaySpecification - -import scala.collection.mutable -import scala.concurrent.Future - -/** - * Test case for the [[com.mohiva.play.silhouette.impl.services.DelegableAuthInfoService]] trait. - */ -class DelegableAuthInfoServiceSpec extends PlaySpecification { - - "The service" should { - "delegate the PasswordInfo to the correct DAO" in new Context { - val loginInfo = LoginInfo("credentials", "1") - - await(service.save(loginInfo, passwordInfo)) must be equalTo passwordInfo - await(service.retrieve[PasswordInfo](loginInfo)) must beSome(passwordInfo) - } - - "delegate the OAuth1Info to the correct DAO" in new Context { - val loginInfo = LoginInfo("credentials", "1") - - await(service.save(loginInfo, oauth1Info)) must be equalTo oauth1Info - await(service.retrieve[OAuth1Info](loginInfo)) must beSome(oauth1Info) - } - - "delegate the OAuth2Info to the correct DAO" in new Context { - val loginInfo = LoginInfo("credentials", "1") - - await(service.save(loginInfo, oauth2Info)) must be equalTo oauth2Info - await(service.retrieve[OAuth2Info](loginInfo)) must beSome(oauth2Info) - } - - "throw an Exception if an unsupported type was given" in new Context { - val loginInfo = LoginInfo("credentials", "1") - - await(service.save(loginInfo, new UnsupportedInfo)) must throwA[Exception].like { - case e => e.getMessage must startWith(SaveError.format("")) - } - } - - "throw an Exception if an unsupported type was given" in new Context { - val loginInfo = LoginInfo("credentials", "1") - - await(service.retrieve[UnsupportedInfo](loginInfo)) must throwA[Exception].like { - case e => e.getMessage must startWith(RetrieveError.format("")) - } - } - } - - /** - * The context. - */ - trait Context extends Scope { - - /** - * The Guice injector. - */ - val injector = Guice.createInjector(new BaseModule) - - /** - * The auth info. - */ - val passwordInfo = PasswordInfo("test.hasher", "test.password") - val oauth1Info = OAuth1Info("test.token", "test.secret") - val oauth2Info = OAuth2Info("test.token") - - /** - * The cache instance to store the different auth information instances. - */ - val service = injector.getInstance(classOf[AuthInfoService]) - } - - /** - * An unsupported auth info. - */ - class UnsupportedInfo extends AuthInfo - - /** - * The Guice module. - * - * This is to test if the [[com.mohiva.play.silhouette.impl.daos.DelegableAuthInfoDAO]] can be used for - * dependency injection because it depends on an implicit [[scala.reflect.ClassTag]] parameter which the - * compiler injects at compile time. - */ - class BaseModule extends AbstractModule with ScalaModule { - - /** - * Configures the module. - */ - def configure() { - bind[DelegableAuthInfoDAO[PasswordInfo]].to[PasswordInfoDAO] - bind[DelegableAuthInfoDAO[OAuth1Info]].to[OAuth1InfoDAO] - bind[DelegableAuthInfoDAO[OAuth2Info]].to[OAuth2InfoDAO] - } - - /** - * Provides the auth info service. - * - * @param passwordInfoDAO The implementation of the delegable password auth info DAO. - * @param oauth1InfoDAO The implementation of the delegable OAuth1 auth info DAO. - * @param oauth2InfoDAO The implementation of the delegable OAuth2 auth info DAO. - * @return The auth info service instance. - */ - @Provides - def provideAuthInfoService( - passwordInfoDAO: DelegableAuthInfoDAO[PasswordInfo], - oauth1InfoDAO: DelegableAuthInfoDAO[OAuth1Info], - oauth2InfoDAO: DelegableAuthInfoDAO[OAuth2Info]): AuthInfoService = { - - new DelegableAuthInfoService(passwordInfoDAO, oauth1InfoDAO, oauth2InfoDAO) - } - } -} - -/** - * The DAO to store the password information. - */ -class PasswordInfoDAO extends DelegableAuthInfoDAO[PasswordInfo] { - - /** - * The data store for the password info. - */ - var data: mutable.HashMap[LoginInfo, PasswordInfo] = mutable.HashMap() - - /** - * Saves the password info. - * - * @param loginInfo The login info for which the auth info should be saved. - * @param authInfo The password info to save. - * @return The saved password info or None if the password info couldn't be saved. - */ - def save(loginInfo: LoginInfo, authInfo: PasswordInfo): Future[PasswordInfo] = { - data += (loginInfo -> authInfo) - Future.successful(authInfo) - } - - /** - * Finds the password info which is linked with the specified login info. - * - * @param loginInfo The linked login info. - * @return The retrieved password info or None if no password info could be retrieved for the given login info. - */ - def find(loginInfo: LoginInfo): Future[Option[PasswordInfo]] = { - Future.successful(Option(data.apply(loginInfo))) - } -} - -/** - * The DAO to store the OAuth1 information. - */ -class OAuth1InfoDAO extends DelegableAuthInfoDAO[OAuth1Info] { - - /** - * The data store for the OAuth1 info. - */ - var data: mutable.HashMap[LoginInfo, OAuth1Info] = mutable.HashMap() - - /** - * Saves the OAuth1 info. - * - * @param loginInfo The login info for which the auth info should be saved. - * @param authInfo The OAuth1 info to save. - * @return The saved OAuth1 info or None if the OAuth1 info couldn't be saved. - */ - def save(loginInfo: LoginInfo, authInfo: OAuth1Info): Future[OAuth1Info] = { - data += (loginInfo -> authInfo) - Future.successful(authInfo) - } - - /** - * Finds the OAuth1 info which is linked with the specified login info. - * - * @param loginInfo The linked login info. - * @return The retrieved OAuth1 info or None if no OAuth1 info could be retrieved for the given login info. - */ - def find(loginInfo: LoginInfo): Future[Option[OAuth1Info]] = { - Future.successful(Option(data.apply(loginInfo))) - } -} - -/** - * The DAO to store the OAuth2 information. - */ -class OAuth2InfoDAO extends DelegableAuthInfoDAO[OAuth2Info] { - - /** - * The data store for the OAuth2 info. - */ - var data: mutable.HashMap[LoginInfo, OAuth2Info] = mutable.HashMap() - - /** - * Saves the OAuth2 info. - * - * @param loginInfo The login info for which the auth info should be saved. - * @param authInfo The OAuth2 info to save. - * @return The saved OAuth2 info or None if the OAuth2 info couldn't be saved. - */ - def save(loginInfo: LoginInfo, authInfo: OAuth2Info): Future[OAuth2Info] = { - data += (loginInfo -> authInfo) - Future.successful(authInfo) - } - - /** - * Finds the OAuth2 info which is linked with the specified login info. - * - * @param loginInfo The linked login info. - * @return The retrieved OAuth2 info or None if no OAuth2 info could be retrieved for the given login info. - */ - def find(loginInfo: LoginInfo): Future[Option[OAuth2Info]] = { - Future.successful(Option(data.apply(loginInfo))) - } -} diff --git a/silhouette/test/com/mohiva/play/silhouette/impl/util/PlayCacheLayerSpec.scala b/silhouette/test/com/mohiva/play/silhouette/impl/util/PlayCacheLayerSpec.scala index d4d527944..09b8a70a2 100644 --- a/silhouette/test/com/mohiva/play/silhouette/impl/util/PlayCacheLayerSpec.scala +++ b/silhouette/test/com/mohiva/play/silhouette/impl/util/PlayCacheLayerSpec.scala @@ -16,62 +16,62 @@ package com.mohiva.play.silhouette.impl.util import org.joda.time.DateTime +import org.specs2.mock.Mockito import org.specs2.specification.Scope -import play.api.cache.Cache -import play.api.test.{ PlaySpecification, WithApplication } -import test.AfterWithinAround +import play.api.cache.CacheApi +import play.api.test.PlaySpecification + +import scala.concurrent.duration.Duration /** * Test case for the [[com.mohiva.play.silhouette.impl.util.PlayCacheLayer]] class. */ -class PlayCacheLayerSpec extends PlaySpecification { +class PlayCacheLayerSpec extends PlaySpecification with Mockito { - "The `save` method" should { - "save value in cache" in new WithApplication with Context { - await(layer.save("id", value)) + "The `find` method" should { + "return value from cache" in new Context { + cacheAPI.get[DateTime]("id") returns Some(value) + + await(layer.find[DateTime]("id")) should beSome(value) - Cache.getAs[DateTime]("id") should beSome(value) + there was one(cacheAPI).get[DateTime]("id") } } - "The `find` method" should { - "return value from cache" in new WithApplication with Context { - Cache.set("id", value) + "The `save` method" should { + "save value in cache" in new Context { + await(layer.save("id", value)) - await(layer.find[DateTime]("id")) should beSome(value) + there was one(cacheAPI).set("id", value, Duration.Inf) } } "The `remove` method" should { - "removes value from cache" in new WithApplication with Context { - Cache.set("id", value) + "removes value from cache" in new Context { + await(layer.remove("id")) must be equalTo (()) - await(layer.remove("id")) - - Cache.getAs[DateTime]("id") should beNone + there was one(cacheAPI).remove("id") } } /** * The context. */ - trait Context extends Scope with AfterWithinAround { - self: WithApplication => + trait Context extends Scope { + /** - * The layer to test. + * The cache API. */ - lazy val layer = new PlayCacheLayer + lazy val cacheAPI = mock[CacheApi] /** - * The value to cache. + * The layer to test. */ - lazy val value = new DateTime + lazy val layer = new PlayCacheLayer(cacheAPI) /** - * Clears the cache after every test + * The value to cache. */ - def after { - Cache.remove("id") - } + lazy val value = new DateTime } }