Skip to content
This repository has been archived by the owner on Sep 12, 2021. It is now read-only.

Commit

Permalink
Merge fccecee into 9218c73
Browse files Browse the repository at this point in the history
  • Loading branch information
akkie committed Apr 7, 2017
2 parents 9218c73 + fccecee commit 48742ff
Show file tree
Hide file tree
Showing 13 changed files with 502 additions and 255 deletions.
Expand Up @@ -19,46 +19,46 @@ package silhouette.persistence.repositories

import javax.inject.Inject

import silhouette.StorableAuthenticator
import silhouette.Authenticator
import silhouette.persistence.CacheLayer
import silhouette.repositories.AuthenticatorRepository

import scala.concurrent.Future
import scala.concurrent.duration.Duration
import scala.reflect.ClassTag

/**
* Implementation of the authenticator repository which uses the cache layer to persist the authenticator.
*
* @param cacheLayer The cache layer implementation.
* @tparam T The type of the authenticator to store.
*/
class CacheAuthenticatorRepository[T <: StorableAuthenticator: ClassTag] @Inject() (cacheLayer: CacheLayer)
extends AuthenticatorRepository[T] {
class CacheAuthenticatorRepository @Inject() (cacheLayer: CacheLayer)
extends AuthenticatorRepository {

/**
* Finds the authenticator for the given ID.
*
* @param id The authenticator ID.
* @return The found authenticator or None if no authenticator could be found for the given ID.
*/
override def find(id: String): Future[Option[T]] = cacheLayer.find[T](id)
override def find(id: String): Future[Option[Authenticator]] = cacheLayer.find[Authenticator](id)

/**
* Adds a new authenticator.
*
* @param authenticator The authenticator to add.
* @return The added authenticator.
*/
override def add(authenticator: T): Future[T] = cacheLayer.save[T](authenticator.id, authenticator, Duration.Inf)
override def add(authenticator: Authenticator): Future[Authenticator] =
cacheLayer.save[Authenticator](authenticator.id, authenticator, Duration.Inf)

/**
* Updates an already existing authenticator.
*
* @param authenticator The authenticator to update.
* @return The updated authenticator.
*/
override def update(authenticator: T): Future[T] = cacheLayer.save[T](authenticator.id, authenticator, Duration.Inf)
override def update(authenticator: Authenticator): Future[Authenticator] =
cacheLayer.save[Authenticator](authenticator.id, authenticator, Duration.Inf)

/**
* Removes the authenticator for the given ID.
Expand Down
Expand Up @@ -17,16 +17,19 @@
*/
package silhouette.persistence.repositories

import java.time.Clock

import org.specs2.concurrent.ExecutionEnv
import org.specs2.mock.Mockito
import org.specs2.mutable.Specification
import org.specs2.specification.Scope
import silhouette.StorableAuthenticator
import silhouette.persistence.CacheLayer
import silhouette.specs2.WaitPatience
import silhouette.{ Authenticator, LoginInfo }

import scala.concurrent.Future
import scala.concurrent.duration.Duration
import scala.reflect.classTag

/**
* Test case for the [[CacheAuthenticatorRepository]] class.
Expand All @@ -38,23 +41,24 @@ class CacheAuthenticatorRepositorySpec(implicit ev: ExecutionEnv)

"The `find` method" should {
"return value from cache" in new Context {
cacheLayer.find[StorableAuthenticator]("test-id") returns Future.successful(Some(authenticator))
implicit val ct = classTag[Authenticator]
cacheLayer.find[Authenticator]("test-id") returns Future.successful(Some(authenticator))

repository.find("test-id") must beSome(authenticator).awaitWithPatience
there was one(cacheLayer).find[StorableAuthenticator]("test-id")
there was one(cacheLayer).find[Authenticator]("test-id")
}

"return None if value couldn't be found in cache" in new Context {
cacheLayer.find[StorableAuthenticator]("test-id") returns Future.successful(None)
implicit val ct = classTag[Authenticator]
cacheLayer.find("test-id") returns Future.successful(None)

repository.find("test-id") must beNone.awaitWithPatience
there was one(cacheLayer).find[StorableAuthenticator]("test-id")
there was one(cacheLayer).find("test-id")
}
}

"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)

repository.add(authenticator) must beEqualTo(authenticator).awaitWithPatience
Expand All @@ -64,7 +68,6 @@ class CacheAuthenticatorRepositorySpec(implicit ev: ExecutionEnv)

"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)

repository.update(authenticator) must beEqualTo(authenticator).awaitWithPatience
Expand All @@ -89,16 +92,21 @@ class CacheAuthenticatorRepositorySpec(implicit ev: ExecutionEnv)
/**
* A storable authenticator.
*/
lazy val authenticator = mock[StorableAuthenticator]
lazy val authenticator = Authenticator(
"test-id",
LoginInfo("", ""),
Clock.systemUTC().instant(),
Clock.systemUTC().instant()
)

/**
* The cache layer implementation.
*/
lazy val cacheLayer = mock[CacheLayer]
lazy val cacheLayer = mock[CacheLayer].smart

/**
* The repository to test.
*/
lazy val repository = new CacheAuthenticatorRepository[StorableAuthenticator](cacheLayer)
lazy val repository = new CacheAuthenticatorRepository(cacheLayer)
}
}
152 changes: 31 additions & 121 deletions silhouette/src/main/scala/silhouette/Authenticator.scala
Expand Up @@ -17,135 +17,45 @@
*/
package silhouette

import java.time.ZonedDateTime
import java.time.Instant

import silhouette.Authenticator.Implicits._
import silhouette.authenticator.AuthenticatorValidator
import silhouette.http.RequestPipeline

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{ ExecutionContext, Future }
import scala.json.ast.JObject

/**
* An authenticator tracks an authenticated user.
*
* @param id The ID of the authenticator.
* @param loginInfo The linked login info for an identity.
* @param lastUsedDateTime The last used date/time.
* @param expirationDateTime The expiration date/time.
* @param fingerprint Maybe a fingerprint of the user.
* @param payload Some custom payload an authenticator can transport.
*/
trait Authenticator {

/**
* The Type of the generated value an authenticator will be serialized to.
*/
type Value

/**
* The type of the settings an authenticator can handle.
*/
type Settings
final case class Authenticator(
id: String,
loginInfo: LoginInfo,
lastUsedDateTime: Instant,
expirationDateTime: Instant,
fingerprint: Option[String] = None,
payload: Option[JObject] = None) {

/**
* Gets the linked login info for an identity.
* Checks if the authenticator is valid.
*
* @return The linked login info for an identity.
*/
def loginInfo: LoginInfo

/**
* Checks if the authenticator valid.
*
* @return True if the authenticator valid, false otherwise.
*/
def isValid: Boolean
}

/**
* The `Authenticator` companion object.
*/
object Authenticator {

/**
* Some implicits.
*/
object Implicits {

/**
* Defines additional methods on an `DateTime` instance.
*
* @param dateTime The `DateTime` instance on which the additional methods should be defined.
*/
implicit class RichDateTime(dateTime: ZonedDateTime) {

/**
* Adds a duration to a date/time.
*
* @param duration The duration to add.
* @return A date/time instance with the added duration.
*/
def +(duration: FiniteDuration): ZonedDateTime = {
dateTime.plusSeconds(duration.toSeconds)
}

/**
* Subtracts a duration from a date/time.
*
* @param duration The duration to subtract.
* @return A date/time instance with the subtracted duration.
*/
def -(duration: FiniteDuration): ZonedDateTime = {
dateTime.minusSeconds(duration.toSeconds)
}
}
* @param validators The list of validators to validate the authenticator with.
* @param request The request pipeline.
* @param ec The execution context to perform the async operations.
* @return True if the authenticator is valid, false otherwise.
*/
def isValid[R](validators: Set[AuthenticatorValidator])(
implicit
request: RequestPipeline[R],
ec: ExecutionContext
): Future[Boolean] = {
Future.sequence(validators.map(_.isValid(this))).map(!_.exists(!_))
}
}

/**
* An authenticator which can be stored in a backing store.
*/
trait StorableAuthenticator extends Authenticator {

/**
* Gets the ID to reference the authenticator in the backing store.
*
* @return The ID to reference the authenticator in the backing store.
*/
def id: String
}

/**
* An authenticator that may expire.
*/
trait ExpirableAuthenticator extends Authenticator {

/**
* The last used date/time.
*/
val lastUsedDateTime: ZonedDateTime

/**
* The expiration date/time.
*/
val expirationDateTime: ZonedDateTime

/**
* The duration an authenticator can be idle before it timed out.
*/
val idleTimeout: Option[FiniteDuration]

/**
* Checks if the authenticator isn't expired and isn't timed out.
*
* @return True if the authenticator isn't expired and isn't timed out.
*/
override def isValid: Boolean = !isExpired && !isTimedOut

/**
* Checks if the authenticator is expired. This is an absolute timeout since the creation of
* the authenticator.
*
* @return True if the authenticator is expired, false otherwise.
*/
def isExpired: Boolean = expirationDateTime.isBefore(ZonedDateTime.now())

/**
* Checks if the time elapsed since the last time the authenticator was used, is longer than
* the maximum idle timeout specified in the properties.
*
* @return True if sliding window expiration is activated and the authenticator is timed out, false otherwise.
*/
def isTimedOut: Boolean = idleTimeout.isDefined && (lastUsedDateTime + idleTimeout.get).isBefore(ZonedDateTime.now())
}
11 changes: 11 additions & 0 deletions silhouette/src/main/scala/silhouette/LoginInfo.scala
Expand Up @@ -17,6 +17,9 @@
*/
package silhouette

import io.circe.generic.semiauto._
import io.circe.{ Decoder, Encoder }

/**
* Represents a linked login for an identity (i.e. a local username/password or a Facebook/Google account).
*
Expand All @@ -26,3 +29,11 @@ package silhouette
* @param providerKey A unique key which identifies a user on this provider (userID, email, ...).
*/
case class LoginInfo(providerID: String, providerKey: String)

/**
* The companion object.
*/
object LoginInfo {
implicit val loginInfoDecoder: Decoder[LoginInfo] = deriveDecoder
implicit val loginInfoEncoder: Encoder[LoginInfo] = deriveEncoder
}
@@ -0,0 +1,38 @@
/**
* Licensed to the Minutemen Group under one or more contributor license
* agreements. See the COPYRIGHT file distributed with this work for
* additional information regarding copyright ownership.
*
* 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 silhouette.authenticator

import silhouette.Authenticator
import silhouette.util.{ Reads, Writes }

import scala.concurrent.Future

/**
* Transforms a string into an [[Authenticator]].
*/
trait AuthenticatorReads extends Reads[String, Future[Authenticator]]

/**
* Transforms an [[Authenticator]] into a string.
*/
trait AuthenticatorWrites extends Writes[Authenticator, Future[String]]

/**
* Authenticator transformer combinator.
*/
trait AuthenticatorFormat extends AuthenticatorReads with AuthenticatorWrites

0 comments on commit 48742ff

Please sign in to comment.