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.