简体   繁体   中英

Play-silhouette-slick-seed; SecuredAction not executing with change of TableQuery

I'm using the play-silhouette-slick seed for a project and I need to add users to different tables depending on their type. However, if I change the TableQuery used to insertUpdate the user in UserDAOImpl.save, a SecuredAction further down the line (for ApplicationController.index) does not execute, suggesting that the user hasn't been authenticated. So by swapping this line of code;

_ <- slickUsers.insertOrUpdate(dbUser)

To this;

_ <-slickAdministrators.insertOrUpdate(dbUser)

My SecuredAction doesn't execute. Can anyone point me in the right direction please? More of my code is below;

class UserDAOImpl extends UserDAO with DAOSlick {

  import driver.api._

  def find(loginInfo: LoginInfo) = {
    getUserTypeAndID(loginInfo) match {
      case Some((id, userType)) if userType.equals("administrator") => val userQuery = for {
                dbUser <- slickUsers.filter(_.userID === id)
              } yield dbUser
                db.run(userQuery.result.headOption).map { dbUserOption =>
                  dbUserOption.map { user =>
                    Administrator(UUID.fromString(user.userID), loginInfo, user.title, user.firstName, user.lastName, user.email)
                  }
                }
  case None => val userQuery = for {
    dbLoginInfo <- loginInfoQuery(loginInfo)
    dbUser <- slickUsers.filter(_.userID === dbLoginInfo.userID)
  } yield dbUser
    db.run(userQuery.result.headOption).map { dbUserOption =>
      dbUserOption.map { user =>
        Administrator(UUID.fromString(user.userID), loginInfo, user.title, user.firstName, user.lastName, user.email)
      }
    }
} 

def getUserTypeAndID(loginInfo: LoginInfo) = {
    val userQuery = for {
      dbLoginInfo <- loginInfoQuery(loginInfo)
    } yield dbLoginInfo
    val dbresult = db.run(userQuery.result.headOption)
    val userOption = Await.result(dbresult, 5 second) 
    userOption map {info => (info.userID, info.userType)}
  }

 def save(user: User) = {
    val dbUser = DBUser(user.userID.toString, user.title, user.firstName, user.lastName, user.email)
    val userType = user.getClass.getTypeName match {
      case "models.Administrator" => "administrator"
    } 
    val loginInfoAction = {
      val retrieveLoginInfo = slickLoginInfos.filter(
        info => info.providerID === user.loginInfo.providerID &&
        info.providerKey === user.loginInfo.providerKey).result.headOption
      val insertLoginInfo = slickLoginInfos += DBLoginInfo(dbUser.userID, user.loginInfo.providerID, user.loginInfo.providerKey, userType)
      for {
        loginInfoOption <- retrieveLoginInfo
        loginInfo <-   loginInfoOption.map(DBIO.successful(_)).getOrElse(insertLoginInfo)
      } yield loginInfo
    }
    val actions = (for {
      _ <- slickAdministrators.insertOrUpdate(dbUser)
      loginInfo <- loginInfoAction
    } yield ()).transactionally
    // run actions and return user afterwards
    db.run(actions).map(_ => user)
}
}

DBTableDefinitions

trait DBTableDefinitions {

  protected val driver: JdbcProfile
  import driver.api._

  case class DBUser (
    userID: String,
title: Option[String],
firstName: Option[String],
lastName: Option[String],
email: Option[String]
  )

  class Users(tag: Tag) extends Table[DBUser](tag, "user") {
    def userID = column[String]("userid", O.PrimaryKey)
    def title = column[Option[String]]("title")
    def firstName = column[Option[String]]("firstname")
    def lastName = column[Option[String]]("lastname")
    def email = column[Option[String]]("email")
    def * = (userID, title, firstName, lastName, email) <> (DBUser.tupled,     DBUser.unapply)
  }

  class Administrators(tag: Tag) extends Table[DBUser](tag, "administrators") {
    def userID = column[String]("userid", O.PrimaryKey)
def title = column[Option[String]]("title")
def firstName = column[Option[String]]("firstname")
def lastName = column[Option[String]]("lastname")
def email = column[Option[String]]("email")
def * = (userID, title, firstName, lastName, email) <> (DBUser.tupled, DBUser.unapply)
  }

case class DBLoginInfo (
     userID: String,
     providerID: String,
     providerKey: String,
     userType: String
  )

  class LoginInfos(tag: Tag) extends Table[DBLoginInfo](tag, "logininfo") {
    def userID = column[String]("userid", O.PrimaryKey)
    def providerID = column[String]("providerid")
def providerKey = column[String]("providerkey")



 def userType = column[String]("usertype")
    def * = (userID, providerID, providerKey, userType) <> (DBLoginInfo.tupled, DBLoginInfo.unapply)
  }



case class DBPasswordInfo (
    hasher: String,
    password: String,
    userID: String
  )

  class PasswordInfos(tag: Tag) extends Table[DBPasswordInfo](tag, "passwordinfo") {
    def hasher = column[String]("hasher")
    def password = column[String]("password")
    def userID = column[String]("userid")
    def * = (hasher, password, userID) <> (DBPasswordInfo.tupled, DBPasswordInfo.unapply)
  }

  // table query definitions
  val slickUsers = TableQuery[Users]
  val slickAdministrators = TableQuery[Administrators]
  val slickLoginInfos = TableQuery[LoginInfos]
  val slickPasswordInfos = TableQuery[PasswordInfos]

  // queries used in multiple places
  def loginInfoQuery(loginInfo: LoginInfo) = 
    slickLoginInfos.filter(dbLoginInfo => dbLoginInfo.providerID === loginInfo.providerID && dbLoginInfo.providerKey === loginInfo.providerKey)

}

SignUpController

class SignUpController @Inject() (
  val messagesApi: MessagesApi,
  val env: Environment[User, CookieAuthenticator],
  userService: UserService,
  authInfoRepository: AuthInfoRepository,
  passwordHasher: PasswordHasher)
  extends Silhouette[User, CookieAuthenticator] {

  /**
   * Registers a new user.
   *
   * @return The result to display.
   */
  def signUp(userType: String) = Action.async { implicit request =>
    SignUpForm.form.bindFromRequest.fold(
      form => Future.successful(BadRequest(views.html.signUp(form))),
      data => {
        val loginInfo = LoginInfo(CredentialsProvider.ID, data.email)
        userService.retrieve(loginInfo).flatMap {
          case Some(user) =>
            Future.successful(Redirect(routes.ApplicationController.signUp()).flashing("error" -> Messages("user.exists")))
          case None =>
            val authInfo = passwordHasher.hash(data.password)
            val user = getUser(userType, data, loginInfo)

            for {
              user <- userService.save(user)
              authInfo <- authInfoRepository.add(loginInfo, authInfo)
            //shouldn't need below data -> it creates cookie info to continue as the user added
              authenticator <- env.authenticatorService.create(loginInfo)
              value <- env.authenticatorService.init(authenticator)
              result <- env.authenticatorService.embed(value, Redirect(routes.ApplicationController.index()))
            } yield {
              env.eventBus.publish(SignUpEvent(user, request, request2Messages))
              env.eventBus.publish(LoginEvent(user, request, request2Messages))
              result
            }
        }
      }
    )
  }

  /**
   * Creates a new user according to the specified userType.
   *
   * @param userType the type of user required
   * @param data the data from the SignUpForm
   * @param loginInfo the users loginInfo
   * @return an instance of a User.
   */
  def getUser(userType: String, data: SignUpForm.Data, loginInfo: LoginInfo): User = userType match{
    case "administrator" => Administrator(
                    userID = UUID.randomUUID(),
                    loginInfo = loginInfo,
                    title = Some(data.title),
                    firstName = Some(data.firstName),
                    lastName = Some(data.lastName),
                    email = Some(data.email)
                  )
  }
}

ApplicationController

class ApplicationController @Inject() (
  val messagesApi: MessagesApi,
  val env: Environment[User, CookieAuthenticator])
  extends Silhouette[User, CookieAuthenticator] {

  /**
   * Handles the index action.
   *
   * @return The result to display.
   */
  def index = SecuredAction.async { implicit request =>
    Future.successful(Ok(views.html.home(request.identity)))
  }

  /**
   * Handles the Sign In action.
   *
   * @return The result to display.
   */
  def signIn = UserAwareAction.async { implicit request =>
    request.identity match {
      case Some(user) => Future.successful(Redirect(routes.ApplicationController.index()))
      case None => Future.successful(Ok(views.html.signIn(SignInForm.form)))
    }
  }

  /**
   * Handles the Sign Up action.
   *
   * @return The result to display.
   */
  def signUp = UserAwareAction.async { implicit request =>
    request.identity match {
      case Some(user) => Future.successful(Redirect(routes.ApplicationController.index()))
      case None => Future.successful(Ok(views.html.signUp(SignUpForm.form)))
    }
  }

  /**
   * Handles the Sign Out action.
   *
   * @return The result to display.
   */
  def signOut = SecuredAction.async { implicit request =>
    val result = Redirect(routes.ApplicationController.index())
    env.eventBus.publish(LogoutEvent(request.identity, request, request2Messages))

    env.authenticatorService.discard(request.authenticator, result)
  }
}

The code above works if I change that single line in UserDAOImpl.save.

The problem was that I had not implemented a method in UserDAOImpl to find a user in the administrators table according to their LoginInfo. So by adding this;

  def find(loginInfo: LoginInfo) = {
    getUserTypeAndID(loginInfo) match {
      case Some((id, userType)) if userType.equals("administrator") => val userQuery = for {
                dbUser <- slickAdministrators.filter(_.userID === id)
              } yield dbUser
                db.run(userQuery.result.headOption).map { dbUserOption =>
                  dbUserOption.map { user =>
                    Administrator(UUID.fromString(user.userID), loginInfo, user.title, user.firstName, user.lastName, user.email)
                  }
                }
  case None => val userQuery = for {
    dbLoginInfo <- loginInfoQuery(loginInfo)
    dbUser <- slickUsers.filter(_.userID === dbLoginInfo.userID)
  } yield dbUser
    db.run(userQuery.result.headOption).map { dbUserOption =>
      dbUserOption.map { user =>
        Administrator(UUID.fromString(user.userID), loginInfo, user.title, user.firstName, user.lastName, user.email)
      }
    }
}

ie, the line

dbUser <- slickAdministrators.filter(_.userID === id)

The SecuredAction now executes as expected for a valid user. I didn't realise that this method was called for authentication for a SecuredAction (or maybe I'm wrong on this and it's being called from somewhere else), so if anyone can explain further it would be really helpful - this has taken me all day to sort!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM