2013-06-14 08:35:34 +00:00
/* *
2016-10-26 11:03:14 +00:00
Open Bank Project - API
2016-11-06 10:48:59 +00:00
Copyright ( C ) 2011 - 2016 , TESOBE Ltd
2016-10-26 11:03:14 +00:00
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU Affero General Public License for more details .
You should have received a copy of the GNU Affero General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
Email : contact @ tesobe.com
2016-11-06 10:48:59 +00:00
TESOBE Ltd
2016-10-26 11:03:14 +00:00
Osloerstrasse 16 / 17
Berlin 13359 , Germany
This product includes software developed at
TESOBE ( http : //www.tesobe.com/)
by
Simon Redfern : simon AT tesobe DOT com
Stefan Bethge : stefan AT tesobe DOT com
Everett Sochowski : everett AT tesobe DOT com
Ayoub Benali : ayoub AT tesobe DOT com
*/
2013-06-14 08:35:34 +00:00
package code.model.dataAccess
2016-09-02 17:53:39 +00:00
import java.util.UUID
2017-03-09 10:51:09 +00:00
import code.api.util.APIUtil.isValidStrongPassword
2016-12-30 23:12:12 +00:00
import code.api.util. { APIUtil , ErrorMessages }
2016-03-28 18:05:42 +00:00
import code.api. { DirectLogin , OAuthHandshake }
2017-03-13 15:44:53 +00:00
import code.bankconnectors. { Connector , InboundUser }
2013-06-14 08:35:34 +00:00
import net.liftweb.common._
2017-01-22 05:54:28 +00:00
import net.liftweb.http._
2016-04-11 08:44:12 +00:00
import net.liftweb.mapper._
import net.liftweb.util.Mailer. { BCC , From , Subject , To }
import net.liftweb.util._
2013-06-14 08:35:34 +00:00
2016-04-11 08:44:12 +00:00
import scala.xml. { NodeSeq , Text }
2016-12-31 15:16:24 +00:00
import code.loginattempts.LoginAttempt
2017-02-22 16:55:50 +00:00
import code.users.Users
2017-02-20 08:28:26 +00:00
import code.util.Helper
2017-03-09 10:51:09 +00:00
import net.liftweb.util
2016-12-31 15:16:24 +00:00
2013-06-14 08:35:34 +00:00
/* *
* An O - R mapped "User" class that includes first name , last name , password
2016-05-25 00:16:09 +00:00
*
*
2017-03-21 14:48:55 +00:00
* // TODO Document the difference between this and AuthUser / ResourceUser
2016-05-25 00:16:09 +00:00
*
2013-06-14 08:35:34 +00:00
*/
2017-02-09 13:34:42 +00:00
class AuthUser extends MegaProtoUser [ AuthUser ] with Logger {
def getSingleton = AuthUser // what's the "meta" server
2014-01-07 09:51:48 +00:00
2017-02-09 13:34:42 +00:00
object user extends MappedLongForeignKey ( this , ResourceUser )
2014-01-07 09:51:48 +00:00
2016-09-02 17:53:39 +00:00
/* *
* The username field for the User .
*/
lazy val username : userName = new userName ( )
class userName extends MappedString ( this , 64 ) {
override def displayName = S . ? ( "username" )
override def dbIndexed_? = true
override def validations = valUnique ( S . ? ( "unique.username" ) ) _ : : super.validations
override val fieldId = Some ( Text ( "txtUsername" ) )
}
2017-03-09 10:51:09 +00:00
override lazy val password = new MyPasswordNew
class MyPasswordNew extends MappedPassword ( this ) {
override def displayName = fieldOwner . passwordDisplayName
private var passwordValue = ""
private var invalidPw = false
private var invalidMsg = ""
override def setFromAny ( f : Any ) : String = {
f match {
case a : Array [ String ] if ( a . length == 2 && a ( 0 ) == a ( 1 ) ) => {
passwordValue = a ( 0 ) . toString ;
if ( isValidStrongPassword ( passwordValue ) )
invalidPw = false
else {
invalidPw = true
invalidMsg = S . ? ( ErrorMessages . InvalidStrongPasswordFormat )
}
this . set ( a ( 0 ) )
}
case l : List [ String ] if ( l . length == 2 && l . head == l ( 1 ) ) => {
passwordValue = l ( 0 ) . toString ;
if ( isValidStrongPassword ( passwordValue ) )
invalidPw = false
else {
invalidPw = true
invalidMsg = S . ? ( ErrorMessages . InvalidStrongPasswordFormat )
}
this . set ( l . head )
}
case _ => {
invalidPw = true ;
invalidMsg = S . ? ( "passwords.do.not.match" )
}
}
get
}
override def validate : List [ FieldError ] = {
if ( super . validate . nonEmpty ) super . validate
else if ( ! invalidPw && password . get != "*" ) Nil
else if ( invalidPw ) List ( FieldError ( this , Text ( invalidMsg ) ) )
else List ( FieldError ( this , Text ( S . ? ( "password.must.be.set" ) ) ) )
}
}
2016-09-02 17:53:39 +00:00
2016-01-15 17:37:06 +00:00
/* *
* The provider field for the User .
*/
lazy val provider : userProvider = new userProvider ( )
class userProvider extends MappedString ( this , 64 ) {
override def displayName = S . ? ( "provider" )
override val fieldId = Some ( Text ( "txtProvider" ) )
}
2014-12-10 16:58:17 +00:00
2016-01-15 17:37:06 +00:00
def getProvider ( ) = {
if ( provider . get == null ) {
Props . get ( "hostname" , "" )
} else if ( provider . get == "" || provider . get == Props . get ( "hostname" , "" ) ) {
Props . get ( "hostname" , "" )
} else {
provider . get
}
}
2017-02-09 13:34:42 +00:00
def createUnsavedResourceUser ( ) : ResourceUser = {
2017-02-22 16:55:50 +00:00
val user = Users . users . vend . createUnsavedResourceUser ( getProvider ( ) , Some ( username ) , Some ( username ) , Some ( email ) , None ) . get
2017-02-20 13:13:18 +00:00
user
2016-09-02 17:53:39 +00:00
}
2017-02-09 13:34:42 +00:00
def getResourceUsersByEmail ( userEmail : String ) : List [ ResourceUser ] = {
2017-02-22 16:55:50 +00:00
Users . users . vend . getUserByEmail ( userEmail ) match {
case Full ( userList ) => userList
case Empty => List ( )
}
2014-12-10 16:58:17 +00:00
}
2017-02-09 13:34:42 +00:00
def getResourceUsers ( ) : List [ ResourceUser ] = {
2017-02-22 16:55:50 +00:00
Users . users . vend . getAllUsers match {
case Full ( userList ) => userList
case Empty => List ( )
}
2016-11-17 14:50:51 +00:00
}
2017-02-09 13:34:42 +00:00
def getResourceUserByUsername ( username : String ) : Box [ ResourceUser ] = {
2017-02-22 16:55:50 +00:00
Users . users . vend . getUserByUserName ( username )
2016-06-20 09:08:37 +00:00
}
2014-12-10 16:58:17 +00:00
override def save ( ) : Boolean = {
if ( ! ( user defined_? ) ) {
2017-03-21 14:48:55 +00:00
info ( "user reference is null. We will create a ResourceUser" )
2017-02-09 13:34:42 +00:00
val resourceUser = createUnsavedResourceUser ( )
2017-02-22 16:55:50 +00:00
val savedUser = Users . users . vend . saveResourceUser ( resourceUser )
2017-02-21 10:59:27 +00:00
user ( savedUser ) //is this saving resourceUser into a user field?
2014-01-07 09:51:48 +00:00
}
else {
2017-03-21 14:48:55 +00:00
info ( "user reference is not null. Trying to update the ResourceUser" )
2017-02-22 16:55:50 +00:00
Users . users . vend . getResourceUserByResourceUserId ( user . get ) . map { u => {
2014-01-07 15:25:15 +00:00
info ( "API User found " )
2016-09-02 17:53:39 +00:00
u . name_ ( username )
2014-01-07 09:51:48 +00:00
. email ( email )
2016-09-02 17:53:39 +00:00
. providerId ( username )
2014-01-07 09:51:48 +00:00
. save
}
}
}
super . save ( )
}
override def delete_! ( ) : Boolean = {
2017-02-27 13:04:20 +00:00
user . obj . map ( u => Users . users . vend . deleteResourceUser ( u . id ) )
2014-01-07 09:51:48 +00:00
super . delete_!
}
2016-08-05 14:05:04 +00:00
// Regex to validate an email address as per W3C recommendations: https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
private val emailRegex = """^[a-zA-Z0-9\.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""" . r
def isEmailValid ( e : String ) : Boolean = e match {
case null => false
case e if e . trim . isEmpty => false
case e if emailRegex . findFirstMatchIn ( e ) . isDefined => true
case _ => false
}
// Override the validate method of MappedEmail class
// There's no way to override the default emailPattern from MappedEmail object
override lazy val email = new MyEmail ( this , 48 ) {
2016-09-02 17:53:39 +00:00
override def validations = super . validations
override def dbIndexed_? = false
2016-08-05 14:05:04 +00:00
override def validate = if ( isEmailValid ( i_is_! ) ) Nil else List ( FieldError ( this , Text ( S . ? ( "invalid.email.address" ) ) ) )
}
2013-06-14 08:35:34 +00:00
}
/* *
* The singleton that has methods for accessing the database
*/
2017-02-09 13:34:42 +00:00
object AuthUser extends AuthUser with MetaMegaProtoUser [ AuthUser ] {
2014-01-07 09:51:48 +00:00
import net.liftweb.util.Helpers._
2016-12-30 23:12:12 +00:00
/* * Marking the locked state to show different error message */
2017-01-17 14:50:14 +00:00
val usernameLockedStateCode = Long . MaxValue
2016-12-30 23:12:12 +00:00
2016-12-19 16:29:36 +00:00
val connector = Props . get ( "connector" ) . openOrThrowException ( "no connector set" )
2014-11-04 11:28:41 +00:00
override def emailFrom = Props . get ( "mail.users.userinfo.sender.address" , "sender-not-set" )
2013-06-14 08:35:34 +00:00
2014-01-07 09:51:48 +00:00
override def screenWrap = Full ( < lift : surround with = "default" at = "content" >< lift : bind /></ lift : surround > )
2013-06-14 08:35:34 +00:00
// define the order fields will appear in forms and output
2016-09-02 17:53:39 +00:00
override def fieldOrder = List ( id , firstName , lastName , email , username , password , provider )
override def signupFields = List ( firstName , lastName , email , username , password )
2013-06-14 08:35:34 +00:00
2017-03-21 14:48:55 +00:00
// If we want to validate email addresses set this to false
override def skipEmailValidation = Props . getBool ( "authUser.skipEmailValidation" , true )
2013-06-14 08:35:34 +00:00
override def loginXhtml = {
2014-04-15 08:50:56 +00:00
val loginXml = Templates ( List ( "templates-hidden" , "_login" ) ) . map ( {
2013-06-14 08:35:34 +00:00
"form [action]" # > { S . uri } &
2015-10-04 18:59:08 +00:00
"#loginText * " # > { S . ? ( "log.in" ) } &
2016-09-02 17:53:39 +00:00
"#usernameText * " # > { S . ? ( "username" ) } &
2015-10-04 18:59:08 +00:00
"#passwordText * " # > { S . ? ( "password" ) } &
2016-12-27 10:08:43 +00:00
"autocomplete=off [autocomplete] " # > APIUtil . getAutocompleteValue &
2013-06-14 08:35:34 +00:00
"#recoverPasswordLink * " # > {
"a [href]" # > { lostPasswordPath . mkString ( "/" , "/" , "" ) } &
2015-10-04 18:59:08 +00:00
"a *" # > { S . ? ( "recover.password" ) }
2013-06-14 08:35:34 +00:00
} &
"#SignUpLink * " # > {
2017-02-09 13:34:42 +00:00
"a [href]" # > { AuthUser . signUpPath . foldLeft ( "" ) ( _ + "/" + _ ) } &
2015-10-04 18:59:08 +00:00
"a *" # > { S . ? ( "sign.up" ) }
2013-06-14 08:35:34 +00:00
}
} )
2015-10-04 18:59:08 +00:00
< div > { loginXml getOrElse NodeSeq . Empty } </ div >
2013-06-14 08:35:34 +00:00
}
2013-07-01 08:06:12 +00:00
2016-03-28 18:05:42 +00:00
/* *
* Find current user
*/
def getCurrentUserUsername : String = {
2017-02-09 13:34:42 +00:00
for {
current <- AuthUser . currentUser
2016-10-31 11:44:01 +00:00
username <- tryo { current . username . get }
if ( username . nonEmpty )
} yield {
return username
}
for {
current <- OAuthHandshake . getUser
username <- tryo { current . name }
if ( username . nonEmpty )
} yield {
return username
}
for {
current <- DirectLogin . getUser
username <- tryo { current . name }
if ( username . nonEmpty )
} yield {
return username
}
2016-03-28 18:05:42 +00:00
return ""
}
2017-01-22 05:54:28 +00:00
/* *
2017-02-09 13:34:42 +00:00
* Find current ResourceUser_UserId from AuthUser , it is only used for Consumer registration form to save the USER_ID when register new consumer .
2017-01-22 05:54:28 +00:00
*/
2017-02-09 13:34:42 +00:00
//TODO may not be a good idea, need modify after refactoring User Models.
def getCurrentResourceUserUserId : String = {
2017-01-22 05:54:28 +00:00
for {
2017-02-09 13:34:42 +00:00
current <- AuthUser . currentUser
2017-02-22 16:55:50 +00:00
user <- Users . users . vend . getUserByResourceUserId ( current . user . get )
2017-01-22 05:54:28 +00:00
} yield {
2017-02-22 15:51:03 +00:00
return user . userId
2017-01-22 05:54:28 +00:00
}
for {
current <- OAuthHandshake . getUser
userId <- tryo { current . userId }
if ( userId . nonEmpty )
} yield {
return userId
}
for {
current <- DirectLogin . getUser
userId <- tryo { current . userId }
if ( userId . nonEmpty )
} yield {
return userId
}
return ""
}
2015-10-04 18:59:08 +00:00
2016-12-16 15:01:34 +00:00
/* *
* The string that 's generated when the user name is not found . By
* default : S . ? ( " email . address . not . found " )
2016-12-17 19:44:00 +00:00
* The function is overridden in order to prevent leak of information at password reset page if username / email exists or do not exist .
* I . e . we want to prevent case in which an anonymous user can get information from the message does some username / email exist or no in our system .
2016-12-16 15:01:34 +00:00
*/
override def userNameNotFoundString : String = "Thank you. If we found a matching user, password reset instructions have been sent."
2014-11-04 11:44:04 +00:00
/* *
2015-10-07 18:18:25 +00:00
* Overridden to use the hostname set in the props file
2014-11-04 11:44:04 +00:00
*/
2016-09-05 20:04:16 +00:00
override def sendPasswordReset ( name : String ) {
findUserByUsername ( name ) match {
2014-11-04 11:44:04 +00:00
case Full ( user ) if user . validated_? =>
user . resetUniqueId ( ) . save
val resetLink = Props . get ( "hostname" , "ERROR" ) +
passwordResetPath . mkString ( "/" , "/" , "/" ) + urlEncode ( user . getUniqueId ( ) )
Mailer . sendMail ( From ( emailFrom ) , Subject ( passwordResetEmailSubject ) ,
2016-05-24 17:14:26 +00:00
To ( user . getEmail ) : :
2014-11-04 11:44:04 +00:00
generateResetEmailBodies ( user , resetLink ) : ::
2016-05-24 17:14:26 +00:00
( bccEmail . toList . map ( BCC ( _ ) ) ) : _ * )
2014-11-04 11:44:04 +00:00
2016-12-16 15:01:34 +00:00
S . notice ( S . ? ( userNameNotFoundString ) )
2014-11-04 11:44:04 +00:00
S . redirectTo ( homePage )
case Full ( user ) =>
sendValidationEmail ( user )
S . notice ( S . ? ( "account.validation.resent" ) )
S . redirectTo ( homePage )
case _ => S . error ( userNameNotFoundString )
}
}
2015-10-04 18:59:08 +00:00
override def lostPasswordXhtml = {
< div id = "authorizeSection" >
< div id = "userAccess" >
< div class = "account account-in-content" >
2016-10-25 14:54:43 +00:00
Enter your email address or username and we 'll email you a link to reset your password
2015-10-04 18:59:08 +00:00
< form class = "forgotPassword" action = { S . uri } method = "post" >
< div class = "field username" >
2016-10-25 14:54:43 +00:00
< label > Username or email address </ label > < user : email />
2015-10-04 18:59:08 +00:00
</ div >
< div class = "field buttons" >
< div class = "button button-field" >
< user : submit />
</ div >
</ div >
</ form >
</ div >
</ div >
</ div >
}
override def lostPassword = {
bind ( "user" , lostPasswordXhtml ,
"email" -> SHtml . text ( "" , sendPasswordReset _ ) ,
"submit" -> lostPasswordSubmitButton ( S . ? ( "submit" ) ) )
}
//override def def passwordResetMailBody(user: TheUserType, resetLink: String): Elem = { }
2014-11-04 11:44:04 +00:00
/* *
* Overriden to use the hostname set in the props file
*/
override def sendValidationEmail ( user : TheUserType ) {
val resetLink = Props . get ( "hostname" , "ERROR" ) + "/" + validateUserPath . mkString ( "/" ) +
"/" + urlEncode ( user . getUniqueId ( ) )
val email : String = user . getEmail
val msgXml = signupMailBody ( user , resetLink )
Mailer . sendMail ( From ( emailFrom ) , Subject ( signupMailSubject ) ,
2016-05-24 17:14:26 +00:00
To ( user . getEmail ) : :
2014-11-04 11:44:04 +00:00
generateValidationEmailBodies ( user , resetLink ) : ::
2016-05-24 17:14:26 +00:00
( bccEmail . toList . map ( BCC ( _ ) ) ) : _ * )
2014-11-04 11:44:04 +00:00
}
2013-06-18 13:03:40 +00:00
/* *
* Set this to redirect to a certain page after a failed login
*/
object failedLoginRedirect extends SessionVar [ Box [ String ] ] ( Empty ) {
override lazy val __nameSalt = Helpers . nextFuncName
}
2013-07-01 08:06:12 +00:00
2016-09-28 11:23:02 +00:00
def agreeTerms = {
val url = Props . get ( "webui_agree_terms_url" , "" )
if ( url . isEmpty ) {
s" "
} else {
scala . xml . Unparsed ( s""" <tr><td colspan= " 2 " ><input type= " checkbox " id= " agree-terms-input " /><span id= " agree-terms-text " >I hereby agree to the <a href= " $url " title= " T & C " >Terms and Conditions</a></span></td></tr> """ )
}
}
2017-02-09 13:34:42 +00:00
override def signupXhtml ( user : AuthUser ) = {
2015-10-04 18:59:08 +00:00
< div id = "authorizeSection" class = "signupSection" >
< div class = "signup-error" >< span class = "lift:Msg?id=signup" /></ div >
< div >
< form id = "signupForm" method = "post" action = { S . uri } >
< table >
< tr >
< td colspan = "2" > { S . ? ( "sign.up" ) } </ td >
</ tr >
{ localForm ( user , false , signupFields ) }
2016-09-28 11:23:02 +00:00
{ agreeTerms }
2015-10-04 18:59:08 +00:00
< tr >
< td >& nbsp ; </ td >
< td >< user : submit /></ td >
</ tr >
</ table >
</ form >
</ div >
</ div >
2014-04-30 15:41:49 +00:00
}
2016-01-15 17:37:06 +00:00
def userLoginFailed = {
info ( "failed: " + failedLoginRedirect . get )
2017-02-20 08:28:26 +00:00
// variable redir is from failedLoginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.failedLoginRedirect.set(Full(Helpers.appendParams(currentUrl, List((FailedLoginParam, "true")))))
val redir = failedLoginRedirect . get
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
S . redirectTo ( redir . toString )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
}
2016-01-15 17:37:06 +00:00
S . error ( "login" , S . ? ( "Invalid Username or Password" ) )
}
2016-05-25 00:16:09 +00:00
2016-12-31 15:16:24 +00:00
2017-02-09 13:34:42 +00:00
def getResourceUserId ( username : String , password : String ) : Box [ Long ] = {
2016-12-31 15:16:24 +00:00
findUserByUsername ( username ) match {
2017-01-23 00:24:47 +00:00
case Full ( user ) if ( user . getProvider ( ) == Props . get ( "hostname" , "" ) ) =>
2016-12-31 15:16:24 +00:00
if (
user . validated_? &&
2016-12-31 18:44:45 +00:00
// User is NOT locked AND the password is good
2016-12-31 15:16:24 +00:00
! LoginAttempt . userIsLocked ( username ) &&
2016-01-29 22:03:30 +00:00
user . testPassword ( Full ( password ) ) )
2016-12-31 15:16:24 +00:00
{
// We logged in correctly, so reset badLoginAttempts counter (if it exists)
LoginAttempt . resetBadLoginAttempts ( username )
Full ( user . user ) // Return the user.
}
2017-01-17 14:50:14 +00:00
// User is unlocked AND password is bad
2016-12-31 15:16:24 +00:00
else if (
user . validated_? &&
2017-01-17 14:50:14 +00:00
! LoginAttempt . userIsLocked ( username ) &&
2016-12-31 15:16:24 +00:00
! user . testPassword ( Full ( password ) )
2016-12-30 23:12:12 +00:00
) {
2016-12-31 15:16:24 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
2016-12-30 23:12:12 +00:00
Empty
}
2016-12-31 18:44:45 +00:00
// User is locked
2017-01-23 00:24:47 +00:00
else if ( LoginAttempt . userIsLocked ( username ) )
{
2017-01-17 14:50:14 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
2016-12-31 15:16:24 +00:00
info ( ErrorMessages . UsernameHasBeenLocked )
2017-01-18 11:57:17 +00:00
//TODO need to fix, use Failure instead, it is used to show the error message to the GUI
2017-02-09 13:34:42 +00:00
Full ( usernameLockedStateCode )
2016-12-30 23:12:12 +00:00
}
2016-01-29 22:03:30 +00:00
else {
2017-02-09 13:34:42 +00:00
// Nothing worked, so just increment bad login attempts
2017-01-23 00:24:47 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
Empty
}
case Full ( user ) if ( user . getProvider ( ) != Props . get ( "hostname" , "" ) ) =>
2016-12-19 16:29:36 +00:00
connector match {
2017-03-06 15:45:47 +00:00
case Helper . matchAnyKafka ( ) if ( Props . getBool ( "kafka.user.authentication" , false ) &&
2017-01-23 00:24:47 +00:00
! LoginAttempt . userIsLocked ( username ) ) =>
val userId = for { kafkaUser <- getUserFromConnector ( username , password )
kafkaUserId <- tryo { kafkaUser . user } } yield {
LoginAttempt . resetBadLoginAttempts ( username )
kafkaUserId . toLong
2017-02-09 13:34:42 +00:00
}
2017-01-23 00:24:47 +00:00
userId match {
case Full ( l : Long ) => Full ( l )
case _ =>
LoginAttempt . incrementBadLoginAttempts ( username )
2017-02-09 13:34:42 +00:00
Empty
2017-01-23 00:24:47 +00:00
}
case "obpjvm" if ( Props . getBool ( "obpjvm.user.authentication" , false ) &&
! LoginAttempt . userIsLocked ( username ) ) =>
val userId = for { obpjvmUser <- getUserFromConnector ( username , password )
2017-02-09 13:34:42 +00:00
obpjvmUserId <- tryo { obpjvmUser . user } } yield {
2017-01-23 00:24:47 +00:00
LoginAttempt . resetBadLoginAttempts ( username )
obpjvmUserId . toLong
}
userId match {
case Full ( l : Long ) => Full ( l )
2017-02-09 13:34:42 +00:00
case _ =>
2017-01-23 00:24:47 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
2017-02-09 13:34:42 +00:00
Empty
2017-01-23 00:24:47 +00:00
}
2017-02-09 13:34:42 +00:00
case _ =>
2017-01-23 00:24:47 +00:00
LoginAttempt . incrementBadLoginAttempts ( username )
Empty
2016-06-21 07:36:27 +00:00
}
2016-12-19 16:29:36 +00:00
2017-01-23 00:24:47 +00:00
case _ =>
LoginAttempt . incrementBadLoginAttempts ( username )
Empty
2016-01-29 22:03:30 +00:00
}
}
2016-11-01 10:43:06 +00:00
2017-02-09 13:34:42 +00:00
def getUserFromConnector ( name : String , password : String ) : Box [ AuthUser ] = {
2016-12-04 21:47:51 +00:00
Connector . connector . vend . getUser ( name , password ) match {
2017-03-13 15:44:53 +00:00
case Full ( InboundUser ( extEmail , extPassword , extUsername ) ) => {
2016-01-29 22:03:30 +00:00
info ( "external user authenticated. login redir: " + loginRedirect . get )
val redir = loginRedirect . get match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
2016-12-19 16:29:36 +00:00
val extProvider = connector
2016-01-29 22:03:30 +00:00
2016-09-05 20:04:16 +00:00
val user = findUserByUsername ( name ) match {
2016-01-29 22:03:30 +00:00
// Check if the external user is already created locally
2016-12-21 18:13:24 +00:00
case Full ( user ) if user . validated_?
2016-12-23 14:30:27 +00:00
// && user.provider == extProvider
2016-12-21 18:13:24 +00:00
=> {
2016-01-29 22:03:30 +00:00
// Return existing user if found
info ( "external user already exists locally, using that one" )
user
}
// If not found, create new user
case _ => {
2017-02-09 13:34:42 +00:00
// Create AuthUser using fetched data from Kafka
2016-01-29 22:03:30 +00:00
// assuming that user's email is always validated
2016-03-28 18:05:42 +00:00
info ( "external user " + extEmail + " does not exist locally, creating one" )
2017-02-09 13:34:42 +00:00
val newUser = AuthUser . create
2016-09-05 20:04:16 +00:00
. firstName ( extUsername )
2016-01-29 22:03:30 +00:00
. email ( extEmail )
2016-09-05 20:04:16 +00:00
. username ( extUsername )
2016-01-29 22:03:30 +00:00
// No need to store password, so store dummy string instead
2016-09-02 17:53:39 +00:00
. password ( UUID . randomUUID ( ) . toString )
2016-01-29 22:03:30 +00:00
. provider ( extProvider )
. validated ( true )
// Save the user in order to be able to log in
newUser . save ( )
// Return created user
newUser
}
}
Full ( user )
}
case _ => {
Empty
}
}
}
2013-06-18 13:03:40 +00:00
//overridden to allow a redirection if login fails
override def login = {
2016-12-23 14:30:27 +00:00
def loginAction = {
if ( S . post_? ) {
2016-12-30 23:12:12 +00:00
val usernameFromGui = S . param ( "username" ) . getOrElse ( "" )
2017-01-12 13:49:10 +00:00
val passwordFromGui = S . param ( "password" ) . getOrElse ( "" )
2017-02-13 18:01:56 +00:00
findUserByUsername ( usernameFromGui ) match {
2017-01-23 00:24:47 +00:00
// Check if user came from localhost and
// if User is NOT locked and password is good
2016-12-23 14:30:27 +00:00
case Full ( user ) if user . validated_? &&
user . getProvider ( ) == Props . get ( "hostname" , "" ) &&
2016-12-31 16:00:43 +00:00
! LoginAttempt . userIsLocked ( usernameFromGui ) &&
2017-01-23 00:24:47 +00:00
user . testPassword ( Full ( passwordFromGui ) ) => {
// Reset any bad attempts
LoginAttempt . resetBadLoginAttempts ( usernameFromGui )
val preLoginState = capturePreLoginState ( )
info ( "login redir: " + loginRedirect . get )
val redir = loginRedirect . get match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
registeredUserHelper ( user . username )
2017-02-20 08:28:26 +00:00
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
2017-01-23 00:24:47 +00:00
logUserIn ( user , ( ) => {
S . notice ( S . ? ( "logged.in" ) )
preLoginState ( )
S . redirectTo ( redir )
} )
2017-02-20 08:28:26 +00:00
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
2013-06-18 13:03:40 +00:00
}
2017-02-20 08:28:26 +00:00
}
2013-06-18 13:03:40 +00:00
2017-02-13 18:01:56 +00:00
// Check if user came from kafka/obpjvm and
// if User is NOT locked. Then check username and password
// from connector in case they changed on the south-side
case Full ( user ) if user . validated_? &&
user . getProvider ( ) != Props . get ( "hostname" , "" ) &&
! LoginAttempt . userIsLocked ( usernameFromGui ) &&
testExternalPassword ( Full ( user . username . get ) , Full ( passwordFromGui ) ) . getOrElse ( false ) => {
// Reset any bad attempts
LoginAttempt . resetBadLoginAttempts ( usernameFromGui )
val preLoginState = capturePreLoginState ( )
info ( "login redir: " + loginRedirect . get )
val redir = loginRedirect . get match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
registeredUserHelper ( user . username )
2017-02-20 08:28:26 +00:00
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
logUserIn ( user , ( ) => {
S . notice ( S . ? ( "logged.in" ) )
preLoginState ( )
S . redirectTo ( redir )
} )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
}
2017-02-13 18:01:56 +00:00
}
2017-01-17 14:50:14 +00:00
// If user is unlocked AND bad password, increment bad login attempt counter.
2016-12-30 23:12:12 +00:00
case Full ( user ) if user . validated_? &&
2017-01-23 00:24:47 +00:00
user . getProvider ( ) == Props . get ( "hostname" , "" ) &&
2017-01-17 14:50:14 +00:00
! LoginAttempt . userIsLocked ( usernameFromGui ) &&
2017-02-09 13:34:42 +00:00
! user . testPassword ( Full ( passwordFromGui ) ) =>
2017-01-12 13:49:10 +00:00
LoginAttempt . incrementBadLoginAttempts ( usernameFromGui )
2016-12-31 16:00:43 +00:00
S . error ( S . ? ( "Invalid Login Credentials" ) ) // TODO constant / i18n for this string
2016-12-30 23:12:12 +00:00
2017-01-22 15:42:59 +00:00
// If user is locked, send the error to GUI
2017-01-23 00:24:47 +00:00
case Full ( user ) if LoginAttempt . userIsLocked ( usernameFromGui ) =>
2017-01-17 14:50:14 +00:00
LoginAttempt . incrementBadLoginAttempts ( usernameFromGui )
2017-01-12 13:49:10 +00:00
S . error ( S . ? ( ErrorMessages . UsernameHasBeenLocked ) )
2016-12-30 23:12:12 +00:00
2016-12-23 14:30:27 +00:00
case Full ( user ) if ! user . validated_? =>
2017-03-21 15:23:25 +00:00
S . error ( S . ? ( "account.validation.error" ) ) // Note: This does not seem to get hit when user is not validated.
2015-05-16 18:38:42 +00:00
2017-01-23 00:24:47 +00:00
// If not found locally, try to authenticate user via Kafka, if enabled in props
2017-03-06 15:45:47 +00:00
case Empty if ( connector . startsWith ( "kafka" ) || connector == "obpjvm" ) &&
2017-01-23 00:24:47 +00:00
( Props . getBool ( "kafka.user.authentication" , false ) ||
2017-02-13 18:01:56 +00:00
Props . getBool ( "obpjvm.user.authentication" , false ) ) =>
2016-12-23 14:30:27 +00:00
val preLoginState = capturePreLoginState ( )
info ( "login redir: " + loginRedirect . get )
val redir = loginRedirect . get match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
for {
2017-01-23 00:24:47 +00:00
user_ <- externalUserHelper ( usernameFromGui , passwordFromGui )
2016-12-23 14:30:27 +00:00
} yield {
2017-02-09 13:34:42 +00:00
user_
2017-01-23 00:24:47 +00:00
} match {
2017-02-09 13:34:42 +00:00
case u : AuthUser =>
2017-01-23 00:24:47 +00:00
LoginAttempt . resetBadLoginAttempts ( usernameFromGui )
2017-02-20 08:28:26 +00:00
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
logUserIn ( u , ( ) => {
S . notice ( S . ? ( "logged.in" ) )
preLoginState ( )
S . redirectTo ( redir )
} )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
}
2017-01-23 00:24:47 +00:00
case _ =>
LoginAttempt . incrementBadLoginAttempts ( username )
Empty
2016-12-23 14:30:27 +00:00
}
2017-01-23 00:24:47 +00:00
case _ =>
LoginAttempt . incrementBadLoginAttempts ( usernameFromGui )
2017-03-21 15:23:25 +00:00
S . error ( S . ? ( ErrorMessages . UnexpectedErrorDuringLogin ) ) // Note we hit this if user has not clicked email validation link
2013-06-18 13:03:40 +00:00
}
}
}
2016-12-23 14:30:27 +00:00
// In this function we bind submit button to loginAction function.
// In case that unique token of submit button cannot be paired submit action will be omitted.
// Implemented in order to prevent a CSRF attack
def insertSubmitButton = {
scala . xml . XML . loadString ( loginSubmitButton ( S . ? ( "Login" ) , loginAction _ ) . toString ( ) . replace ( "type=\"submit\"" , "class=\"submit\" type=\"submit\"" ) )
}
2013-06-18 13:03:40 +00:00
bind ( "user" , loginXhtml ,
2016-12-23 14:30:27 +00:00
"submit" -> insertSubmitButton )
2013-06-18 13:03:40 +00:00
}
2013-06-14 08:35:34 +00:00
2016-11-01 10:43:06 +00:00
2017-02-13 18:01:56 +00:00
def testExternalPassword ( usernameFromGui : Box [ String ] , passwordFromGui : Box [ String ] ) : Box [ Boolean ] = {
2017-03-06 15:45:47 +00:00
if ( connector . startsWith ( "kafka" ) || connector == "obpjvm" ) {
2017-02-13 18:01:56 +00:00
val res = for {
username <- usernameFromGui
password <- passwordFromGui
user <- getUserFromConnector ( username , password )
} yield user match {
case user : AuthUser => true
case _ => false
}
res
} else Empty
}
2017-02-09 13:34:42 +00:00
def externalUserHelper ( name : String , password : String ) : Box [ AuthUser ] = {
2017-03-06 15:45:47 +00:00
if ( connector . startsWith ( "kafka" ) || connector == "obpjvm" ) {
2016-07-27 18:29:14 +00:00
for {
2016-12-19 16:29:36 +00:00
user <- getUserFromConnector ( name , password )
2017-02-22 16:55:50 +00:00
u <- Users . users . vend . getUserByUserName ( username )
2016-12-04 21:47:51 +00:00
v <- tryo { Connector . connector . vend . updateUserAccountViews ( u ) }
2016-07-27 18:29:14 +00:00
} yield {
user
}
2016-10-15 22:20:26 +00:00
} else Empty
2016-07-27 18:29:14 +00:00
}
2016-11-01 10:43:06 +00:00
2016-10-26 11:03:14 +00:00
def registeredUserHelper ( username : String ) = {
2017-03-06 15:45:47 +00:00
if ( connector . startsWith ( "kafka" ) || connector == "obpjvm" ) {
2016-10-26 11:03:14 +00:00
for {
2017-02-22 16:55:50 +00:00
u <- Users . users . vend . getUserByUserName ( username )
2016-12-04 21:47:51 +00:00
v <- tryo { Connector . connector . vend . updateUserAccountViews ( u ) }
2016-10-26 11:03:14 +00:00
} yield v
}
}
2016-07-27 18:29:14 +00:00
2016-09-05 20:04:16 +00:00
protected def findUserByUsername ( name : String ) : Box [ TheUserType ] = {
find ( By ( this . username , name ) )
}
2016-07-27 18:29:14 +00:00
2014-04-15 08:51:36 +00:00
//overridden to allow redirect to loginRedirect after signup. This is mostly to allow
// loginFirst menu items to work if the user doesn't have an account. Without this,
// if a user tries to access a logged-in only page, and then signs up, they don't get redirected
// back to the proper page.
override def signup = {
val theUser : TheUserType = mutateUserOnSignup ( createNewUserInstance ( ) )
val theName = signUpPath . mkString ( "" )
2017-02-20 08:28:26 +00:00
//Check the internal redirect, in case for open redirect issue.
// variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
// val currentUrl = S.uriAndQueryString.getOrElse("/")
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
2014-04-15 08:51:36 +00:00
val loginRedirectSave = loginRedirect . is
def testSignup ( ) {
validateSignup ( theUser ) match {
case Nil =>
2017-02-20 08:28:26 +00:00
//here we check loginRedirectSave (different from implementation in super class)
val redir = loginRedirectSave match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
actionsAfterSignup ( theUser , ( ) => {
S . redirectTo ( redir )
} )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
}
2014-04-15 08:51:36 +00:00
2015-10-04 18:59:08 +00:00
case xs =>
xs . foreach ( e => S . error ( "signup" , e . msg ) )
signupFunc ( Full ( innerSignup _ ) )
2014-04-15 08:51:36 +00:00
}
}
def innerSignup = bind ( "user" ,
signupXhtml ( theUser ) ,
"submit" -> signupSubmitButton ( S . ? ( "sign.up" ) , testSignup _ ) )
innerSignup
}
2016-01-15 17:37:06 +00:00
}