<commit_before>package com.blinkbox.books.auth.server.services

import com.blinkbox.books.auth.server.ZuulRequestErrorCode.InvalidRequest
import com.blinkbox.books.auth.server._
import com.blinkbox.books.auth.server.data.{Client, _}
import com.blinkbox.books.auth.server.events.{Publisher, UserAuthenticated, UserRegistered, UserUpdated}
import com.blinkbox.books.auth.server.sso._
import com.blinkbox.books.slick.DatabaseSupport
import com.blinkbox.books.time.Clock
import spray.http.RemoteAddress

import scala.concurrent.{ExecutionContext, Future}
import scala.slick.profile.BasicProfile
import scala.util.Random

trait PasswordAuthenticationService {
  def authenticate(credentials: PasswordCredentials, clientIP: Option[RemoteAddress]): Future[TokenInfo]
}

class DefaultPasswordAuthenticationService[Profile <: BasicProfile, Database <: Profile#Backend#Database](
    db: Database,
    authRepo: AuthRepository[Profile],
    userRepo: UserRepository[Profile],
    tokenBuilder: TokenBuilder,
    events: Publisher,
    ssoSync: SsoSyncService,
    sso: Sso)(implicit executionContext: ExecutionContext, clock: Clock)
  extends PasswordAuthenticationService
    with ClientAuthenticator[Profile]
    with SsoMigrationPublisher {

  private def findUser(username: String): Future[Option[User]] = Future {
    db.withSession { implicit session => userRepo.userWithUsername(username) }
  }

  private def getClient(credentials: PasswordCredentials, user: User): Future[Option[Client]] = Future {
    db.withSession { implicit session => authenticateClient(authRepo, credentials, user.id) }
  }

  private def getToken(userId: UserId, clientId: Option[ClientId], refreshToken: SsoRefreshToken) : Future[RefreshToken] = Future {
    db.withSession { implicit session => authRepo.createRefreshToken(userId, clientId, refreshToken) }
  }

  def authenticate(credentials: PasswordCredentials, clientIP: Option[RemoteAddress]): Future[TokenInfo] = {
    val ssoAuthenticationFuture = sso authenticate (credentials)

    for {
      SsoAuthenticatedCredentials(
        _, ssoCredentials, status)  <- ssoAuthenticationFuture
      maybeUser                     <- findUser(credentials.username)
      user                          <- ssoSync(maybeUser, ssoCredentials.accessToken)
      client                        <- getClient(credentials, user)
      token                         <- getToken(user.id, client.map(_.id), ssoCredentials.refreshToken)
      _                             <- events.publish(UserAuthenticated(user, client))
      _                             <- events.publishSsoMigration(user, status)
    } yield tokenBuilder.issueAccessToken(user, client, token, Some(ssoCredentials), includeRefreshToken = true)

  } transform(identity, {
    case SsoUnauthorized => Failures.invalidUsernamePassword
    case SsoInvalidRequest(msg) => Failures.requestException(msg, InvalidRequest)
    case SsoTooManyRequests(retryAfter) => Failures.tooManyRequests(retryAfter)
    case e: Throwable => e
  })
}<commit_msg>CP-1864 Parallelise some operations in the authentication<commit_after>package com.blinkbox.books.auth.server.services

import com.blinkbox.books.auth.server.ZuulRequestErrorCode.InvalidRequest
import com.blinkbox.books.auth.server._
import com.blinkbox.books.auth.server.data.{Client, _}
import com.blinkbox.books.auth.server.events.{Publisher, UserAuthenticated, UserRegistered, UserUpdated}
import com.blinkbox.books.auth.server.sso._
import com.blinkbox.books.slick.DatabaseSupport
import com.blinkbox.books.time.Clock
import spray.http.RemoteAddress

import scala.concurrent.{ExecutionContext, Future}
import scala.slick.profile.BasicProfile
import scala.util.Random

trait PasswordAuthenticationService {
  def authenticate(credentials: PasswordCredentials, clientIP: Option[RemoteAddress]): Future[TokenInfo]
}

class DefaultPasswordAuthenticationService[Profile <: BasicProfile, Database <: Profile#Backend#Database](
    db: Database,
    authRepo: AuthRepository[Profile],
    userRepo: UserRepository[Profile],
    tokenBuilder: TokenBuilder,
    events: Publisher,
    ssoSync: SsoSyncService,
    sso: Sso)(implicit executionContext: ExecutionContext, clock: Clock)
  extends PasswordAuthenticationService
    with ClientAuthenticator[Profile]
    with SsoMigrationPublisher {

  private def findUser(username: String): Future[Option[User]] = Future {
    db.withSession { implicit session => userRepo.userWithUsername(username) }
  }

  private def getClient(credentials: PasswordCredentials, user: User): Future[Option[Client]] = Future {
    db.withSession { implicit session => authenticateClient(authRepo, credentials, user.id) }
  }

  private def getToken(userId: UserId, clientId: Option[ClientId], refreshToken: SsoRefreshToken) : Future[RefreshToken] = Future {
    db.withSession { implicit session => authRepo.createRefreshToken(userId, clientId, refreshToken) }
  }

  def authenticate(credentials: PasswordCredentials, clientIP: Option[RemoteAddress]): Future[TokenInfo] = {
    val ssoAuthenticationFuture = sso authenticate (credentials)
    val findUserFuture = findUser(credentials.username)

    for {
      SsoAuthenticatedCredentials(
        _, ssoCredentials, status)  <- ssoAuthenticationFuture
      maybeUser                     <- findUserFuture
      user                          <- ssoSync(maybeUser, ssoCredentials.accessToken)
      client                        <- getClient(credentials, user)
      ((token, _), _)               <- getToken(user.id, client.map(_.id), ssoCredentials.refreshToken).
                                       zip(events.publish(UserAuthenticated(user, client))).
                                       zip(events.publishSsoMigration(user, status))
    } yield tokenBuilder.issueAccessToken(user, client, token, Some(ssoCredentials), includeRefreshToken = true)

  } transform(identity, {
    case SsoUnauthorized => Failures.invalidUsernamePassword
    case SsoInvalidRequest(msg) => Failures.requestException(msg, InvalidRequest)
    case SsoTooManyRequests(retryAfter) => Failures.tooManyRequests(retryAfter)
    case e: Throwable => e
  })
}
{'fraction_non_alphanumeric': 0.09378052751546727, 'fraction_numerical': 0.0006512536633018561, 'mean_word_length': 4.144891122278057, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5016786', 'n_tokens_mistral': 1698, 'n_tokens_neox': 1688, 'n_words': 327}