2013-06-14 08:35:34 +00:00
/* *
2016-10-26 11:03:14 +00:00
Open Bank Project - API
2019-11-07 16:32:09 +00:00
Copyright ( C ) 2011 - 2019 , TESOBE GmbH .
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
2019-11-07 16:30:37 +00:00
TESOBE GmbH .
2019-01-16 14:29:18 +00:00
Osloer Strasse 16 / 17
2016-10-26 11:03:14 +00:00
Berlin 13359 , Germany
2019-01-16 14:29:18 +00:00
This product includes software developed at
TESOBE ( http : //www.tesobe.com/)
2016-10-26 11:03:14 +00:00
*/
2013-06-14 08:35:34 +00:00
package code.model.dataAccess
2020-06-22 08:14:29 +00:00
import code.UserRefreshes.UserRefreshes
2019-03-10 15:17:00 +00:00
import code.accountholders.AccountHolders
2025-06-13 15:12:17 +00:00
import code.api._
2023-10-06 09:26:12 +00:00
import code.api.cache.Caching
2022-05-11 21:57:52 +00:00
import code.api.dynamic.endpoint.helper.DynamicEndpointHelper
2022-08-04 14:44:29 +00:00
import code.api.util.APIUtil._
2025-06-11 12:21:01 +00:00
import code.api.util.CommonFunctions.validUri
2018-12-20 13:22:35 +00:00
import code.api.util.ErrorMessages._
import code.api.util._
2019-09-09 11:51:40 +00:00
import code.bankconnectors.Connector
2021-03-16 07:26:34 +00:00
import code.context.UserAuthContextProvider
2021-06-29 12:38:50 +00:00
import code.entitlement.Entitlement
2016-12-31 15:16:24 +00:00
import code.loginattempts.LoginAttempt
2022-08-30 13:30:50 +00:00
import code.snippet.WebUI
2021-09-13 09:59:17 +00:00
import code.token.TokensOpenIDConnect
2022-08-30 13:30:50 +00:00
import code.users. { UserAgreementProvider , Users }
2017-02-20 08:28:26 +00:00
import code.util.Helper
2023-09-11 13:04:48 +00:00
import code.util.Helper. { MdcLoggable , ObpS }
2025-06-11 12:21:01 +00:00
import code.util.HydraUtil._
2017-07-13 13:19:12 +00:00
import code.views.Views
2025-06-11 12:21:01 +00:00
import code.webuiprops.MappedWebUiPropsProvider.getWebUiPropsValue
import com.openbankproject.commons.ExecutionContext.Implicits.global
2022-08-04 14:44:29 +00:00
import com.openbankproject.commons.model._
2025-06-11 12:21:01 +00:00
import com.tesobe.CacheKeyFromArguments
2018-12-20 13:22:35 +00:00
import net.liftweb.common._
2025-06-11 12:21:01 +00:00
import net.liftweb.http.S.fmapFunc
2018-12-20 13:22:35 +00:00
import net.liftweb.http._
import net.liftweb.mapper._
2025-06-11 12:21:01 +00:00
import net.liftweb.sitemap.Loc. { If , LocParam , Template }
2018-12-20 13:22:35 +00:00
import net.liftweb.util.Mailer. { BCC , From , Subject , To }
import net.liftweb.util._
2020-09-01 13:48:32 +00:00
import org.apache.commons.lang3.StringUtils
2022-06-06 14:54:06 +00:00
import sh.ory.hydra.api.AdminApi
2025-06-11 12:21:01 +00:00
import sh.ory.hydra.model.AcceptLoginRequest
2016-12-31 15:16:24 +00:00
2025-06-11 12:21:01 +00:00
import java.util.UUID.randomUUID
2021-06-30 23:12:18 +00:00
import scala.concurrent.Future
2025-06-11 12:21:01 +00:00
import scala.xml. { Elem , NodeSeq , Text }
2021-06-30 23:12:18 +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-05-31 12:30:33 +00:00
* 1 AuthUser : is used for authentication , only for webpage Login in stuff
* 1 ) It is MegaProtoUser , has lots of methods for validation username , password , email . . . .
* Such as lost password , reset password . . . . .
* Lift have some helper methods to make these things easily .
*
*
*
* 2 ResourceUser : is only a normal LongKeyedMapper
* 1 ) All the accounts , transactions , roles , views , accountHolders , customers . . . should be linked to ResourceUser . userId_ field .
* 2 ) The consumer keys , tokens are also belong ResourceUser
*
*
* 3 RelationShips :
2023-01-03 15:59:06 +00:00
* 1 ) When `Sign up` new user --> create AuthUser --> call AuthUser . save --> create ResourceUser user .
2017-05-31 12:30:33 +00:00
* They share the same username and email .
* 2 ) AuthUser `user` field as the Foreign Key to link to Resource User .
* one AuthUser <---> one ResourceUser
2016-05-25 00:16:09 +00:00
*
2013-06-14 08:35:34 +00:00
*/
2021-12-24 12:47:57 +00:00
class AuthUser extends MegaProtoUser [ AuthUser ] with CreatedUpdated with MdcLoggable {
2017-02-09 13:34:42 +00:00
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 )
2025-02-17 08:57:33 +00:00
object passwordShouldBeChanged extends MappedBoolean ( this )
2014-01-07 09:51:48 +00:00
2020-05-11 13:05:39 +00:00
override lazy val firstName = new MyFirstName
2021-09-02 08:40:13 +00:00
protected class MyFirstName extends MappedString ( this , 100 ) {
2020-05-11 13:05:39 +00:00
def isEmpty ( msg : => String ) ( value : String ) : List [ FieldError ] =
value match {
case null => List ( FieldError ( this , Text ( msg ) ) ) // issue 179
case e if e . trim . isEmpty => List ( FieldError ( this , Text ( msg ) ) ) // issue 179
case _ => Nil
}
override def displayName = fieldOwner . firstNameDisplayName
override val fieldId = Some ( Text ( "txtFirstName" ) )
override def validations = isEmpty ( Helper . i18n ( "Please.enter.your.first.name" ) ) _ : : super.validations
2020-09-11 08:50:54 +00:00
override def _toForm : Box [ Elem ] =
fmapFunc ( { s : List [ String ] => this . setFromAny ( s ) } ) { name =>
Full ( appendFieldId ( < input type = { formInputType }
maxlength = { maxLen . toString }
aria - labelledby = { displayName }
aria - describedby = { uniqueFieldId . getOrElse ( "" ) }
name = { name }
value = { get match { case null => "" case s => s . toString } } /> ) )
}
2020-05-11 13:05:39 +00:00
}
override lazy val lastName = new MyLastName
2021-09-02 08:40:13 +00:00
protected class MyLastName extends MappedString ( this , 100 ) {
2020-05-11 13:05:39 +00:00
def isEmpty ( msg : => String ) ( value : String ) : List [ FieldError ] =
value match {
case null => List ( FieldError ( this , Text ( msg ) ) ) // issue 179
case e if e . trim . isEmpty => List ( FieldError ( this , Text ( msg ) ) ) // issue 179
case _ => Nil
}
override def displayName = fieldOwner . lastNameDisplayName
override val fieldId = Some ( Text ( "txtLastName" ) )
override def validations = isEmpty ( Helper . i18n ( "Please.enter.your.last.name" ) ) _ : : super.validations
2020-09-11 08:50:54 +00:00
override def _toForm : Box [ Elem ] =
fmapFunc ( { s : List [ String ] => this . setFromAny ( s ) } ) { name =>
Full ( appendFieldId ( < input type = { formInputType }
maxlength = { maxLen . toString }
aria - labelledby = { displayName }
aria - describedby = { uniqueFieldId . getOrElse ( "" ) }
name = { name }
value = { get match { case null => "" case s => s . toString } } /> ) )
}
2020-05-11 13:05:39 +00:00
}
2020-09-24 08:21:32 +00:00
/* *
2021-09-20 09:51:44 +00:00
* Username is a valid email address or the regex below :
2020-09-24 08:21:32 +00:00
* Regex to validate a username
*
* ^ ( ?= . { 8 , 100 } $ ) ( ?! [ _ . ] ) ( ?! . * [ _ . ] { 2 } ) [ a - zA - Z0 - 9 . _ ] + ( ?<! [ _ . ] ) $
* └─────┬────┘└───┬──┘└─────┬─────┘└─────┬─────┘ └───┬───┘
* │ │ │ │ no _ or . at the end
* │ │ │ │
* │ │ │ allowed characters
* │ │ │
* │ │ no __ or _ . or . _ or . . inside
* │ │
* │ no _ or . at the beginning
* │
* username is 8 - 100 characters long
*
*/
private val usernameRegex = """^(?=.{8,100}$)(?![_.])(?!.*[_.]{2})[a-zA-Z0-9._]+(?<![_.])$""" . r
2016-09-02 17:53:39 +00:00
/* *
* The username field for the User .
*/
lazy val username : userName = new userName ( )
2021-09-02 08:40:13 +00:00
class userName extends MappedString ( this , 100 ) {
2020-05-11 13:05:39 +00:00
def isEmpty ( msg : => String ) ( value : String ) : List [ FieldError ] =
value match {
case null => List ( FieldError ( this , Text ( msg ) ) ) // issue 179
case e if e . trim . isEmpty => List ( FieldError ( this , Text ( msg ) ) ) // issue 179
case _ => Nil
}
2020-09-24 09:58:02 +00:00
def usernameIsValid ( msg : => String ) ( e : String ) = e match {
2020-09-24 08:21:32 +00:00
case null => List ( FieldError ( this , Text ( msg ) ) )
case e if e . trim . isEmpty => List ( FieldError ( this , Text ( msg ) ) )
2021-09-20 09:51:44 +00:00
case e if emailRegex . findFirstMatchIn ( e ) . isDefined => Nil // Email is valid username
2020-09-24 08:21:32 +00:00
case e if usernameRegex . findFirstMatchIn ( e ) . isDefined => Nil
case _ => List ( FieldError ( this , Text ( msg ) ) )
}
2025-02-17 08:57:33 +00:00
override def displayName = Helper . i18n ( "Username" )
2021-12-27 11:24:08 +00:00
@deprecated ( "Use UniqueIndex(username, provider)" , "27 December 2021" )
override def dbIndexed_? = false // We use more general index UniqueIndex(username, provider) :: super.dbIndexes
2020-09-24 08:21:32 +00:00
override def validations = isEmpty ( Helper . i18n ( "Please.enter.your.username" ) ) _ : :
2020-09-24 09:58:02 +00:00
usernameIsValid ( Helper . i18n ( "invalid.username" ) ) _ : :
2020-08-10 06:29:44 +00:00
valUnique ( Helper . i18n ( "unique.username" ) ) _ : :
valUniqueExternally ( Helper . i18n ( "unique.username" ) ) _ : :
2020-08-11 07:43:07 +00:00
super . validations
2016-09-02 17:53:39 +00:00
override val fieldId = Some ( Text ( "txtUsername" ) )
2020-08-10 06:29:44 +00:00
2020-09-11 08:50:54 +00:00
override def _toForm : Box [ Elem ] =
fmapFunc ( { s : List [ String ] => this . setFromAny ( s ) } ) { name =>
Full ( appendFieldId ( < input type = { formInputType }
maxlength = { maxLen . toString }
aria - labelledby = { displayName }
aria - describedby = { uniqueFieldId . getOrElse ( "" ) }
name = { name }
value = { get match { case null => "" case s => s . toString } } /> ) )
}
2020-08-10 06:29:44 +00:00
/* *
* Make sure that the field is unique in the CBS
*/
2020-08-11 07:43:07 +00:00
def valUniqueExternally ( msg : => String ) ( uniqueUsername : String ) : List [ FieldError ] = {
2020-08-10 06:29:44 +00:00
if ( APIUtil . getPropsAsBoolValue ( "connector.user.authentication" , false ) ) {
2020-08-11 07:43:07 +00:00
Connector . connector . vend . checkExternalUserExists ( uniqueUsername , None ) . map ( _ . sub ) match {
case Full ( returnedUsername ) => // Get the username via connector
if ( uniqueUsername == returnedUsername ) { // Username is NOT unique
List ( FieldError ( this , Text ( msg ) ) ) // provide the error message
} else {
Nil // All good. Allow username creation
2020-08-10 06:29:44 +00:00
}
2020-08-11 07:43:07 +00:00
case ParamFailure ( message , _ , _ , APIFailure ( errorMessage , errorCode ) ) if errorMessage . contains ( "NO DATA" ) => // Cannot get the username via connector
Nil // All good. Allow username creation
case _ => // Any other case we provide error message
List ( FieldError ( this , Text ( msg ) ) )
2020-08-10 06:29:44 +00:00
}
} else {
2020-08-11 07:43:07 +00:00
Nil // All good. Allow username creation
2020-08-10 06:29:44 +00:00
}
}
2016-09-02 17:53:39 +00:00
}
2017-03-09 10:51:09 +00:00
override lazy val password = new MyPasswordNew
2020-06-09 11:28:31 +00:00
lazy val signupPasswordRepeatText = getWebUiPropsValue ( "webui_signup_body_password_repeat_text" , S . ? ( "repeat" ) )
2017-03-09 10:51:09 +00:00
class MyPasswordNew extends MappedPassword ( this ) {
2020-06-09 11:28:31 +00:00
lazy val preFilledPassword = if ( APIUtil . getPropsAsBoolValue ( "allow_pre_filled_password" , true ) ) { get . toString } else ""
2020-04-26 08:19:05 +00:00
override def _toForm : Box [ NodeSeq ] = {
S . fmapFunc ( { s : List [ String ] => this . setFromAny ( s ) } ) { funcName =>
2020-05-11 13:05:39 +00:00
Full (
< span >
2020-09-11 08:50:54 +00:00
{ appendFieldId ( < input id = "textPassword" aria - labelledby = "Password" aria - describedby = { uniqueFieldId . getOrElse ( "" ) } type = { formInputType } name = { funcName } value = { preFilledPassword } /> ) }
2020-05-11 13:05:39 +00:00
< div id = "signup-error" class = "alert alert-danger hide" >
< span data - lift = { s" Msg?id= ${ uniqueFieldId . getOrElse ( "" ) } &errorClass=error " } />
</ div >
< div id = "repeat-password" > { signupPasswordRepeatText } </ div >
2020-09-11 08:50:54 +00:00
< input id = "textPasswordRepeat" aria - labelledby = "Password Repeat" aria - describedby = { uniqueFieldId . getOrElse ( "" ) } type = { formInputType } name = { funcName } value = { preFilledPassword } />
2020-05-11 13:05:39 +00:00
< div id = "signup-error" class = "alert alert-danger hide" >
< span data - lift = { s" Msg?id= ${ uniqueFieldId . getOrElse ( "" ) } _repeat&errorClass=error " } />
</ div >
</ span > )
2020-04-26 08:19:05 +00:00
}
}
2017-03-09 10:51:09 +00:00
override def displayName = fieldOwner . passwordDisplayName
private var passwordValue = ""
private var invalidPw = false
private var invalidMsg = ""
2017-04-28 07:04:50 +00:00
// TODO Remove double negative and abreviation.
// TODO “invalidPw” = false -> “strongPassword = true” etc.
2017-03-09 10:51:09 +00:00
override def setFromAny ( f : Any ) : String = {
2020-05-11 13:05:39 +00:00
def checkPassword ( ) = {
def isPasswordEmpty ( ) = {
if ( passwordValue . isEmpty ( ) )
true
else {
passwordValue match {
case "*" | null | MappedPassword . blankPw =>
true
case _ =>
false
}
}
}
isPasswordEmpty ( ) match {
case true =>
invalidPw = true ;
invalidMsg = Helper . i18n ( "please.enter.your.password" )
S . error ( "authuser_password_repeat" , Text ( Helper . i18n ( "please.re-enter.your.password" ) ) )
case false =>
2022-08-04 14:44:29 +00:00
if ( fullPasswordValidation ( passwordValue ) )
2020-05-11 13:05:39 +00:00
invalidPw = false
else {
invalidPw = true
invalidMsg = S . ? ( ErrorMessages . InvalidStrongPasswordFormat . split ( ':' ) ( 1 ) )
S . error ( "authuser_password_repeat" , Text ( invalidMsg ) )
}
}
}
2017-03-09 10:51:09 +00:00
f match {
case a : Array [ String ] if ( a . length == 2 && a ( 0 ) == a ( 1 ) ) => {
2018-12-31 11:34:50 +00:00
passwordValue = a ( 0 ) . toString
2020-05-11 13:05:39 +00:00
checkPassword ( )
2017-03-09 10:51:09 +00:00
this . set ( a ( 0 ) )
}
2018-12-31 11:34:50 +00:00
case l : List [ _ ] if ( l . length == 2 && l . head . asInstanceOf [ String ] == l ( 1 ) . asInstanceOf [ String ] ) => {
passwordValue = l ( 0 ) . asInstanceOf [ String ]
2020-05-11 13:05:39 +00:00
checkPassword ( )
2018-12-31 11:34:50 +00:00
this . set ( l . head . asInstanceOf [ String ] )
2017-03-09 10:51:09 +00:00
}
case _ => {
invalidPw = true ;
2020-05-11 13:05:39 +00:00
invalidMsg = Helper . i18n ( "passwords.do.not.match" )
S . error ( "authuser_password_repeat" , Text ( invalidMsg ) )
2017-03-09 10:51:09 +00:00
}
}
get
}
override def validate : List [ FieldError ] = {
2020-05-11 20:08:29 +00:00
if ( ! invalidPw && password . get != "*" ) super . validate
else if ( invalidPw ) List ( FieldError ( this , Text ( invalidMsg ) ) ) ++ super . validate
else List ( FieldError ( this , Text ( Helper . i18n ( "please.enter.your.password" ) ) ) ) ++ super . validate
2017-03-09 10:51:09 +00:00
}
}
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 ( )
2021-09-02 08:40:13 +00:00
class userProvider extends MappedString ( this , 100 ) {
2016-01-15 17:37:06 +00:00
override def displayName = S . ? ( "provider" )
override val fieldId = Some ( Text ( "txtProvider" ) )
2021-12-28 13:42:13 +00:00
override def validations = validUri ( this ) _ : : super.validations
2023-03-03 11:53:59 +00:00
override def defaultValue : String = Constant . localIdentityProvider
2016-01-15 17:37:06 +00:00
}
2014-12-10 16:58:17 +00:00
2016-01-15 17:37:06 +00:00
def getProvider ( ) = {
2023-03-03 11:53:59 +00:00
if ( provider . get == null || provider . get == "" ) {
2023-03-02 15:18:25 +00:00
Constant . localIdentityProvider
2016-01-15 17:37:06 +00:00
} else {
provider . get
}
}
2017-02-09 13:34:42 +00:00
def createUnsavedResourceUser ( ) : ResourceUser = {
2018-01-31 15:03:46 +00:00
val user = Users . users . vend . createUnsavedResourceUser ( getProvider ( ) , Some ( username . get ) , Some ( username . get ) , Some ( email . get ) , None ) . openOrThrowException ( attemptedToOpenAnEmptyBox )
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
2018-10-30 12:35:02 +00:00
case _ => List ( )
2017-02-22 16:55:50 +00:00
}
2014-12-10 16:58:17 +00:00
}
2024-06-07 10:14:23 +00:00
def getResourceUserByProviderAndUsername ( provider : String , username : String ) : Box [ User ] = {
Users . users . vend . getUserByProviderAndUsername ( provider , username )
2016-06-20 09:08:37 +00:00
}
2014-12-10 16:58:17 +00:00
override def save ( ) : Boolean = {
if ( ! ( user defined_? ) ) {
2019-09-10 14:01:29 +00:00
logger . 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 {
2019-09-10 14:01:29 +00:00
logger . 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 => {
2019-09-10 14:01:29 +00:00
logger . info ( "API User found " )
2017-08-04 07:00:59 +00:00
u . name_ ( username . get )
. email ( email . get )
. providerId ( username . get )
2014-01-07 09:51:48 +00:00
. save
}
}
}
2023-01-03 15:59:06 +00:00
super . save
2014-01-07 09:51:48 +00:00
}
override def delete_! ( ) : Boolean = {
2017-08-04 07:00:59 +00:00
user . obj . map ( u => Users . users . vend . deleteResourceUser ( u . id . get ) )
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
2020-05-11 13:05:39 +00:00
override def validate = i_is_! match {
case null => List ( FieldError ( this , Text ( Helper . i18n ( "Please.enter.your.email" ) ) ) )
case e if e . trim . isEmpty => List ( FieldError ( this , Text ( Helper . i18n ( "Please.enter.your.email" ) ) ) )
2025-02-17 08:57:33 +00:00
case e if ( ! isEmailValid ( e ) ) => List ( FieldError ( this , Text ( Helper . i18n ( "invalid.email.address" ) ) ) )
2020-05-11 13:05:39 +00:00
case _ => Nil
}
2020-09-11 08:50:54 +00:00
override def _toForm : Box [ Elem ] =
fmapFunc ( { s : List [ String ] => this . setFromAny ( s ) } ) { name =>
Full ( appendFieldId ( < input type = { formInputType }
maxlength = { maxLen . toString }
aria - labelledby = { displayName }
aria - describedby = { uniqueFieldId . getOrElse ( "" ) }
name = { name }
value = { get match { case null => "" case s => s . toString } } /> ) )
}
2016-08-05 14:05:04 +00:00
}
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
2025-07-09 07:47:38 +00:00
val connector = code . api . Constant . CONNECTOR . openOrThrowException ( s" $MandatoryPropertyIsNotSet . The missing prop is `connector` " )
2020-07-14 08:33:12 +00:00
val starConnectorSupportedTypes = APIUtil . getPropsValue ( "starConnector_supported_types" , "" )
2016-12-19 16:29:36 +00:00
2021-12-27 11:24:08 +00:00
override def dbIndexes : List [ BaseIndex [ AuthUser ] ] = UniqueIndex ( username , provider ) : : super.dbIndexes
2025-06-11 12:21:01 +00:00
override def emailFrom = Constant . mailUsersUserinfoSenderAddress
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
2021-06-29 11:34:27 +00:00
// To force validation of email addresses set this to false (default as of 29 June 2021)
override def skipEmailValidation = APIUtil . getPropsAsBoolValue ( "authUser.skipEmailValidation" , false )
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 ( {
2023-09-13 15:36:20 +00:00
"form [action]" # > { ObpS . 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" ) } &
2023-09-11 13:04:48 +00:00
"#login_challenge [value]" # > ObpS . param ( "login_challenge" ) . getOrElse ( "" ) &
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
}
2023-10-06 09:26:12 +00:00
// Update ResourceUser.LastUsedLocale only once per session in 60 seconds
def updateComputedLocale ( sessionId : String , computedLocale : String ) : Boolean = {
/* *
* Please note that "var cacheKey = (randomUUID().toString, randomUUID().toString, randomUUID().toString)"
* is just a temporary value field with UUID values in order to prevent any ambiguity .
* The real value will be assigned by Macro during compile time at this line of a code :
* https : //github.com/OpenBankProject/scala-macros/blob/master/macros/src/main/scala/com/tesobe/CacheKeyFromArgumentsMacro.scala#L49
*/
import scala.concurrent.duration._
val ttl : Duration = FiniteDuration ( 60 , "second" )
var cacheKey = ( randomUUID ( ) . toString , randomUUID ( ) . toString , randomUUID ( ) . toString )
CacheKeyFromArguments . buildCacheKey {
Caching . memoizeSyncWithProvider ( Some ( cacheKey . toString ( ) ) ) ( ttl ) {
logger . debug ( s" AuthUser.updateComputedLocale(sessionId = $sessionId , computedLocale = $computedLocale ) " )
getCurrentUser . map ( _ . userPrimaryKey . value ) match {
case Full ( id ) =>
Users . users . vend . getResourceUserByResourceUserId ( id ) . map {
u =>
u . LastUsedLocale ( computedLocale ) . save
logger . debug ( s" ResourceUser.LastUsedLocale is saved for the resource user id: $id " )
} . isDefined
case _ => true // There is no current user
}
}
}
}
2017-07-11 10:04:58 +00:00
2017-01-22 05:54:28 +00:00
/* *
2017-07-11 10:04:58 +00:00
* Find current ResourceUser from the server .
2017-03-29 08:34:29 +00:00
* This method has no parameters , it depends on different login types :
* AuthUser : AuthUser.currentUser
* OAuthHandshake : OAuthHandshake.getUser
* DirectLogin : DirectLogin.getUser
2017-07-11 10:04:58 +00:00
* to get the current Resourceuser .
*
2017-01-22 05:54:28 +00:00
*/
2017-07-11 10:04:58 +00:00
def getCurrentUser : Box [ User ] = {
2021-06-26 07:13:08 +00:00
val authorization : Box [ String ] = S . request . map ( _ . header ( "Authorization" ) ) . flatten
val directLogin : Box [ String ] = S . request . map ( _ . header ( "DirectLogin" ) ) . flatten
2017-01-22 05:54:28 +00:00
for {
2023-01-25 14:04:29 +00:00
resourceUser <- if ( AuthUser . currentUser . isDefined ) {
//AuthUser.currentUser.get.user.foreign // this will be issue when the resource user is in remote side {
val user = AuthUser . currentUser . openOrThrowException ( ErrorMessages . attemptedToOpenAnEmptyBox )
2023-01-31 10:31:58 +00:00
// In case that the provider is empty field we default to "local_identity_provider" or "hostname"
2023-02-27 09:30:30 +00:00
val provider =
if ( user . provider . get == null || user . provider . get . isEmpty )
Constant . localIdentityProvider
else
user . provider . get
2024-06-07 10:14:23 +00:00
Users . users . vend . getUserByProviderAndUsername ( provider , user . username . get )
2023-02-27 09:30:30 +00:00
} else if ( directLogin . isDefined ) // Direct Login
2021-06-26 07:13:08 +00:00
DirectLogin . getUser
else if ( hasDirectLoginHeader ( authorization ) ) // Direct Login Deprecated
2017-07-11 10:04:58 +00:00
DirectLogin . getUser
2018-01-15 21:05:24 +00:00
else if ( hasAnOAuthHeader ( authorization ) ) {
2017-07-11 08:29:19 +00:00
OAuthHandshake . getUser
2018-01-15 21:05:24 +00:00
} else if ( hasGatewayHeader ( authorization ) ) {
2017-11-30 12:16:57 +00:00
GatewayLogin . getUser
2017-07-11 10:04:58 +00:00
} else {
2019-09-10 14:01:29 +00:00
logger . debug ( ErrorMessages . CurrentUserNotFoundException )
2017-07-11 10:04:58 +00:00
Failure ( ErrorMessages . CurrentUserNotFoundException )
}
2017-01-22 05:54:28 +00:00
} yield {
2017-07-11 10:04:58 +00:00
resourceUser
2017-01-22 05:54:28 +00:00
}
2017-07-11 10:04:58 +00:00
}
/* *
* get current user .
* Note : 1 . it will call getCurrentUser method ,
*
*/
def getCurrentUserUsername : String = {
2020-04-30 10:56:32 +00:00
getCurrentUser match {
2021-11-02 16:06:01 +00:00
case Full ( user ) if user . provider . contains ( "google" ) && ! user . emailAddress . isEmpty => user . emailAddress
case Full ( user ) if user . provider . contains ( "yahoo" ) && ! user . emailAddress . isEmpty => user . emailAddress
case Full ( user ) if user . provider . contains ( "microsoft" ) && ! user . emailAddress . isEmpty => user . emailAddress
2017-07-11 10:04:58 +00:00
case Full ( user ) => user . name
case _ => "" //TODO need more error handling for different user cases
}
}
2021-09-13 09:59:17 +00:00
def getIDTokenOfCurrentUser ( ) : String = {
if ( APIUtil . getPropsAsBoolValue ( "openid_connect.show_tokens" , false ) ) {
AuthUser . currentUser match {
case Full ( authUser ) =>
TokensOpenIDConnect . tokens . vend . getOpenIDConnectTokenByAuthUser ( authUser . id . get ) . map ( _ . idToken ) . getOrElse ( "" )
case _ => ""
}
} else {
2021-09-13 13:41:49 +00:00
"This information is not allowed at this instance."
2021-09-13 09:59:17 +00:00
}
2022-06-07 05:54:27 +00:00
}
def getAccessTokenOfCurrentUser ( ) : String = {
if ( APIUtil . getPropsAsBoolValue ( "openid_connect.show_tokens" , false ) ) {
AuthUser . currentUser match {
case Full ( authUser ) =>
TokensOpenIDConnect . tokens . vend . getOpenIDConnectTokenByAuthUser ( authUser . id . get ) . map ( _ . accessToken ) . getOrElse ( "" )
case _ => ""
}
} else {
"This information is not allowed at this instance."
}
2021-09-13 09:59:17 +00:00
}
2017-07-11 10:04:58 +00:00
/* *
* get current user . userId
* Note : 1 . resourceuser has two ids : id ( Long ) and userid_ ( String ) ,
*
* @return return userid_ ( String ) .
*/
def getCurrentResourceUserUserId : String = {
getCurrentUser match {
case Full ( user ) => user . userId
case _ => "" //TODO need more error handling for different user cases
2017-01-22 05:54:28 +00:00
}
2017-07-11 10:04:58 +00:00
}
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 ) {
2024-06-06 11:16:50 +00:00
findAuthUserByUsernameLocallyLegacy ( name ) . toList : :: findUsersByEmailLocally ( name ) map {
2019-06-15 09:50:23 +00:00
// reason of case parameter name is "u" instead of "user": trait AuthUser have constant mumber name is "user"
// So if the follow case paramter name is "user" will cause compile warnings
case u if u . validated_? =>
u . resetUniqueId ( ) . save
2019-11-21 08:47:42 +00:00
//NOTE: here, if server_mode = portal, so we need modify the resetLink to portal_hostname, then developer can get proper response..
2021-12-22 14:39:34 +00:00
val resetPasswordLinkProps = Constant . HostName
2019-12-02 09:27:22 +00:00
val resetPasswordLink = APIUtil . getPropsValue ( "portal_hostname" , resetPasswordLinkProps ) +
2019-06-15 09:50:23 +00:00
passwordResetPath . mkString ( "/" , "/" , "/" ) + urlEncode ( u . getUniqueId ( ) )
Mailer . sendMail ( From ( emailFrom ) , Subject ( passwordResetEmailSubject + " - " + u . username ) ,
To ( u . getEmail ) : :
2019-12-02 09:27:22 +00:00
generateResetEmailBodies ( u , resetPasswordLink ) : ::
2016-05-24 17:14:26 +00:00
( bccEmail . toList . map ( BCC ( _ ) ) ) : _ * )
2019-06-15 09:50:23 +00:00
case u =>
sendValidationEmail ( u )
2014-11-04 11:44:04 +00:00
}
2019-04-19 08:59:23 +00:00
// In order to prevent any leakage of information we use the same message for all cases
S . notice ( userNameNotFoundString )
S . redirectTo ( homePage )
2014-11-04 11:44:04 +00:00
}
2015-10-04 18:59:08 +00:00
override def lostPasswordXhtml = {
2020-10-27 09:26:53 +00:00
< div id = "recover-password" tabindex = "-1" >
2017-07-21 10:47:42 +00:00
< h1 > Recover Password </ h1 >
< div id = "recover-password-explanation" > Enter your email address or username and we 'll email you a link to reset your password </ div >
2023-09-13 15:36:20 +00:00
< form action = { ObpS . uri } method = "post" >
2017-07-21 10:47:42 +00:00
< div class = "form-group" >
2017-11-01 16:56:35 +00:00
< label > Username or email address </ label > < span id = "recover-password-email" >< input id = "email" type = "text" /></ span >
2015-10-04 18:59:08 +00:00
</ div >
2017-07-21 10:47:42 +00:00
< div id = "recover-password-submit" >
2017-11-01 16:56:35 +00:00
< input type = "submit" />
2015-10-04 18:59:08 +00:00
</ div >
</ form >
</ div >
}
override def lostPassword = {
2017-08-04 07:00:59 +00:00
val bind =
2017-11-01 16:56:35 +00:00
"#email" # > SHtml . text ( "" , sendPasswordReset _ ) &
2017-08-04 07:00:59 +00:00
"type=submit" # > lostPasswordSubmitButton ( S . ? ( "submit" ) )
bind ( lostPasswordXhtml )
2015-10-04 18:59:08 +00:00
}
//override def def passwordResetMailBody(user: TheUserType, resetLink: String): Elem = { }
2014-11-04 11:44:04 +00:00
/* *
2020-04-30 10:56:32 +00:00
* Overridden to use the hostname set in the props file
2014-11-04 11:44:04 +00:00
*/
override def sendValidationEmail ( user : TheUserType ) {
2021-12-22 14:39:34 +00:00
val resetLink = Constant . HostName + "/" + validateUserPath . mkString ( "/" ) +
2014-11-04 11:44:04 +00:00
"/" + 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
}
2024-03-19 10:21:51 +00:00
2020-10-07 08:35:14 +00:00
def grantDefaultEntitlementsToAuthUser ( user : TheUserType ) = {
2024-06-07 10:14:23 +00:00
tryo { getResourceUserByProviderAndUsername ( user . getProvider ( ) , user . username . get ) . head . userId } match {
2020-09-25 13:48:51 +00:00
case Full ( userId ) => APIUtil . grantDefaultEntitlementsToNewUser ( userId )
case _ => logger . error ( "Can not getResourceUserByUsername here, so it breaks the grantDefaultEntitlementsToNewUser process." )
}
}
2024-03-19 10:21:51 +00:00
2020-09-25 13:48:51 +00:00
override def validateUser ( id : String ) : NodeSeq = findUserByUniqueId ( id ) match {
case Full ( user ) if ! user . validated_? =>
user . setValidated ( true ) . resetUniqueId ( ) . save
grantDefaultEntitlementsToAuthUser ( user )
logUserIn ( user , ( ) => {
S . notice ( S . ? ( "account.validated" ) )
2022-04-21 07:33:32 +00:00
APIUtil . getPropsValue ( "user_account_validated_redirect_url" ) match {
2024-03-19 10:21:51 +00:00
case Full ( redirectUrl ) =>
2022-04-21 07:33:32 +00:00
logger . debug ( s" user_account_validated_redirect_url = $redirectUrl " )
S . redirectTo ( redirectUrl )
case _ =>
logger . debug ( s" user_account_validated_redirect_url is NOT defined " )
S . redirectTo ( homePage )
}
2020-09-25 13:48:51 +00:00
} )
2014-11-04 11:44:04 +00:00
2020-09-25 13:48:51 +00:00
case _ => S . error ( S . ? ( "invalid.validation.link" ) ) ; S . redirectTo ( homePage )
}
2024-03-19 10:21:51 +00:00
2020-09-25 13:48:51 +00:00
override def actionsAfterSignup ( theUser : TheUserType , func : ( ) => Nothing ) : Nothing = {
theUser . setValidated ( skipEmailValidation ) . resetUniqueId ( )
theUser . save
2022-08-31 13:29:19 +00:00
val privacyPolicyValue : String = getWebUiPropsValue ( "webui_privacy_policy" , "" )
2022-08-30 13:30:50 +00:00
val termsAndConditionsValue : String = getWebUiPropsValue ( "webui_terms_and_conditions" , "" )
// User Agreement table
2024-09-10 11:39:01 +00:00
UserAgreementProvider . userAgreementProvider . vend . createUserAgreement (
2022-08-31 13:29:19 +00:00
theUser . user . foreign . map ( _ . userId ) . getOrElse ( "" ) , "privacy_conditions" , privacyPolicyValue )
2024-09-10 11:39:01 +00:00
UserAgreementProvider . userAgreementProvider . vend . createUserAgreement (
2022-08-30 13:30:50 +00:00
theUser . user . foreign . map ( _ . userId ) . getOrElse ( "" ) , "terms_and_conditions" , termsAndConditionsValue )
2020-09-25 13:48:51 +00:00
if ( ! skipEmailValidation ) {
sendValidationEmail ( theUser )
S . notice ( S . ? ( "sign.up.message" ) )
func ( )
} else {
grantDefaultEntitlementsToAuthUser ( theUser )
logUserIn ( theUser , ( ) => {
S . notice ( S . ? ( "welcome" ) )
func ( )
} )
}
}
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
2020-04-26 08:19:05 +00:00
def agreeTermsDiv = {
2022-08-30 13:30:50 +00:00
val webUi = new WebUI
val webUiPropsValue = getWebUiPropsValue ( "webui_terms_and_conditions" , "" )
2022-09-15 08:42:18 +00:00
val termsAndConditionsCheckboxTitle = Helper . i18n ( "terms_and_conditions_checkbox_text" , Some ( "I agree to the above Terms and Conditions" ) )
val termsAndConditionsCheckboxLabel = Helper . i18n ( "terms_and_conditions_checkbox_label" , Some ( "Terms and Conditions" ) )
2022-08-30 13:30:50 +00:00
val agreeTermsHtml = s""" <hr>
2022-08-29 14:06:11 +00:00
| < div class = "form-group" id = "terms-and-conditions-div" onclick = "enableDisableButton()" >
| < details open style = "cursor:s-resize;" >
2022-09-15 08:42:18 +00:00
| < summary style = "display:list-item;" >< a class = "api_group_name" > $termsAndConditionsCheckboxLabel </ a ></ summary >
2022-08-30 13:30:50 +00:00
| < div id = "terms-and-conditions-page" > $ { webUi . makeHtml ( webUiPropsValue ) } </ div >
2022-08-29 14:06:11 +00:00
| </ details >
| < input type = "checkbox" class = "form-check-input" id = "terms_checkbox" >
2022-09-15 08:42:18 +00:00
| < label id = "terms_checkbox_value" class = "form-check-label" for = "terms_checkbox" > $termsAndConditionsCheckboxTitle </ label >
2022-08-29 14:06:11 +00:00
| </ div >
| "" " . stripMargin
scala . xml . Unparsed ( agreeTermsHtml )
2016-09-28 11:23:02 +00:00
}
2020-08-12 21:04:37 +00:00
def legalNoticeDiv = {
val agreeTermsHtml = getWebUiPropsValue ( "webui_legal_notice_html_text" , "" )
if ( agreeTermsHtml . isEmpty ) {
s" "
} else {
scala . xml . Unparsed ( s""" $agreeTermsHtml """ )
}
}
2024-03-19 10:21:51 +00:00
2018-05-24 10:14:52 +00:00
def agreePrivacyPolicy = {
2022-08-30 13:30:50 +00:00
val webUi = new WebUI
2022-09-15 08:42:18 +00:00
val privacyPolicyCheckboxText = Helper . i18n ( "privacy_policy_checkbox_text" , Some ( "I agree to the above Privacy Policy" ) )
val privacyPolicyCheckboxLabel = Helper . i18n ( "privacy_policy_checkbox_label" , Some ( "Privacy Policy" ) )
2022-08-30 13:30:50 +00:00
val webUiPropsValue = getWebUiPropsValue ( "webui_privacy_policy" , "" )
val agreePrivacyPolicy = s""" <hr>
2022-08-29 14:06:11 +00:00
| < div class = "form-group" id = "privacy-conditions-div" onclick = "enableDisableButton()" >
| < details open style = "cursor:s-resize;" >
2022-09-15 08:42:18 +00:00
| < summary style = "display:list-item;" >< a class = "api_group_name" > $privacyPolicyCheckboxLabel </ a ></ summary >
2022-08-30 13:30:50 +00:00
| < div id = "privacy-policy-page" > $ { webUi . makeHtml ( webUiPropsValue ) } </ div >
2022-08-29 14:06:11 +00:00
| </ details >
| < input id = "privacy_checkbox" type = "checkbox" class = "form-check-input" >
2022-09-15 08:42:18 +00:00
| < label class = "form-check-label" for = "privacy_checkbox" > $privacyPolicyCheckboxText </ label >
2022-08-29 14:06:11 +00:00
| </ div >
| < hr > "" " . stripMargin
scala . xml . Unparsed ( agreePrivacyPolicy )
2024-03-19 10:21:51 +00:00
}
2022-08-29 14:06:11 +00:00
def enableDisableSignUpButton = {
val javaScriptCode = "" " < script >
| function enableDisableButton ( ) {
| var checkBox = document . getElementById ( "terms-and-conditions-div" ) . querySelector ( "input[type=checkbox]" ) ;
| var checkBox2 = document . getElementById ( "privacy-conditions-div" ) . querySelector ( "input[type=checkbox]" ) ;
| var button = document . getElementById ( "submit-button" ) ;
| if ( checkBox . checked == true && checkBox2 . checked == true ) {
| button . disabled = false ;
| } else {
| button . disabled = true ;
| }
| }
| </ script > "" " . stripMargin
scala . xml . Unparsed ( javaScriptCode )
2018-05-24 10:14:52 +00:00
}
2020-04-26 08:19:05 +00:00
def signupFormTitle = getWebUiPropsValue ( "webui_signup_form_title_text" , S . ? ( "sign.up" ) )
2024-03-19 10:21:51 +00:00
2017-02-09 13:34:42 +00:00
override def signupXhtml ( user : AuthUser ) = {
2020-10-27 09:26:53 +00:00
< div id = "signup" tabindex = "-1" >
2023-09-13 15:36:20 +00:00
< form method = "post" action = { ObpS . uriAndQueryString . getOrElse ( ObpS . uri ) } >
2020-04-26 08:19:05 +00:00
< h1 > { signupFormTitle } </ h1 >
2020-08-12 21:04:37 +00:00
{ legalNoticeDiv }
2020-05-11 13:05:39 +00:00
< div id = "signup-general-error" class = "alert alert-danger hide" >< span data - lift = "Msg?id=error" /></ div >
2017-07-21 10:47:42 +00:00
{ localForm ( user , false , signupFields ) }
2020-04-26 08:19:05 +00:00
{ agreeTermsDiv }
2018-05-24 10:14:52 +00:00
{ agreePrivacyPolicy }
2017-07-21 10:47:42 +00:00
< div id = "signup-submit" >
2022-08-29 14:06:11 +00:00
< input onmouseover = "enableDisableButton()" onfocus = "enableDisableButton()" disabled = "true" id = "submit-button" type = "submit" class = "btn btn-danger" />
2017-07-21 10:47:42 +00:00
</ div >
2022-08-29 14:06:11 +00:00
{ enableDisableSignUpButton }
2017-07-21 10:47:42 +00:00
</ form >
2015-10-04 18:59:08 +00:00
</ div >
2014-04-30 15:41:49 +00:00
}
2018-05-24 10:14:52 +00:00
2017-07-21 10:47:42 +00:00
override def localForm ( user : TheUserType , ignorePassword : Boolean , fields : List [ FieldPointerType ] ) : NodeSeq = {
for {
pointer <- fields
field <- computeFieldFromPointer ( user , pointer ) . toList
if field . show_? && ( ! ignorePassword || ! pointer . isPasswordField_? )
form <- field . toForm . toList
2020-05-11 13:05:39 +00:00
} yield {
if ( field . uniqueFieldId . getOrElse ( "" ) == "authuser_password" ) {
< div class = "form-group" >
< label > { field . displayName } </ label >
{ form }
</ div >
} else {
< div class = "form-group" >
< label > { field . displayName } </ label >
{ form }
< div id = "signup-error" class = "alert alert-danger hide" >< span data - lift = { s" Msg?id= ${ field . uniqueFieldId . getOrElse ( "" ) } &errorClass=error " } /></ div >
</ div >
}
}
2024-03-19 10:21:51 +00:00
2017-07-21 10:47:42 +00:00
}
2016-01-15 17:37:06 +00:00
def userLoginFailed = {
2019-09-10 14:01:29 +00:00
logger . 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:
2023-09-13 14:21:02 +00:00
// val currentUrl = ObpS.uriAndQueryString.getOrElse("/")
2017-02-20 08:28:26 +00:00
// 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:
2023-09-13 14:21:02 +00:00
// val currentUrl = ObpS.uriAndQueryString.getOrElse("/")
2017-02-20 08:28:26 +00:00
// 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 ) )
2019-09-10 14:01:29 +00:00
logger . info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
2017-02-20 08:28:26 +00:00
}
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 ] = {
2024-06-06 11:16:50 +00:00
findAuthUserByUsernameLocallyLegacy ( username ) match {
2021-03-15 16:05:09 +00:00
// We have a user from the local provider.
2021-12-30 17:27:54 +00:00
case Full ( user ) if ( user . getProvider ( ) == Constant . localIdentityProvider ) =>
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
2023-01-25 14:04:29 +00:00
! LoginAttempt . userIsLocked ( user . getProvider ( ) , 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)
2023-01-25 14:04:29 +00:00
LoginAttempt . resetBadLoginAttempts ( user . getProvider ( ) , username )
2017-08-04 07:00:59 +00:00
Full ( user . user . get ) // Return the user.
2016-12-31 15:16:24 +00:00
}
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_? &&
2023-01-25 14:04:29 +00:00
! LoginAttempt . userIsLocked ( user . getProvider ( ) , username ) &&
2016-12-31 15:16:24 +00:00
! user . testPassword ( Full ( password ) )
2016-12-30 23:12:12 +00:00
) {
2023-01-25 14:04:29 +00:00
LoginAttempt . incrementBadLoginAttempts ( user . getProvider ( ) , username )
2016-12-30 23:12:12 +00:00
Empty
}
2016-12-31 18:44:45 +00:00
// User is locked
2023-01-25 14:04:29 +00:00
else if ( LoginAttempt . userIsLocked ( user . getProvider ( ) , username ) )
2017-01-23 00:24:47 +00:00
{
2023-01-25 14:04:29 +00:00
LoginAttempt . incrementBadLoginAttempts ( user . getProvider ( ) , username )
2019-09-10 14:01:29 +00:00
logger . 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
2023-01-25 14:04:29 +00:00
LoginAttempt . incrementBadLoginAttempts ( user . getProvider ( ) , username )
2017-01-23 00:24:47 +00:00
Empty
}
2021-03-15 16:05:09 +00:00
// We have a user from an external provider.
2021-12-30 17:27:54 +00:00
case Full ( user ) if ( user . getProvider ( ) != Constant . localIdentityProvider ) =>
2021-03-15 16:05:09 +00:00
APIUtil . getPropsAsBoolValue ( "connector.user.authentication" , false ) match {
2023-01-25 14:04:29 +00:00
case true if ! LoginAttempt . userIsLocked ( user . getProvider ( ) , username ) =>
2021-03-15 16:05:09 +00:00
val userId =
for {
authUser <- checkExternalUserViaConnector ( username , password )
resourceUser <- tryo {
authUser . user
2020-06-22 14:20:39 +00:00
}
2021-03-15 16:05:09 +00:00
} yield {
2023-01-25 14:04:29 +00:00
LoginAttempt . resetBadLoginAttempts ( user . getProvider ( ) , username )
2021-03-15 16:05:09 +00:00
resourceUser . get
2020-06-22 14:20:39 +00:00
}
2021-03-15 16:05:09 +00:00
userId match {
case Full ( l : Long ) => Full ( l )
case _ =>
2023-01-25 14:04:29 +00:00
LoginAttempt . incrementBadLoginAttempts ( user . getProvider ( ) , username )
2021-03-15 16:05:09 +00:00
Empty
}
case false =>
2023-01-25 14:04:29 +00:00
LoginAttempt . incrementBadLoginAttempts ( user . getProvider ( ) , username )
2017-01-23 00:24:47 +00:00
Empty
2016-06-21 07:36:27 +00:00
}
2021-03-15 16:05:09 +00:00
// Everything else.
2017-01-23 00:24:47 +00:00
case _ =>
2023-01-25 14:04:29 +00:00
LoginAttempt . incrementBadLoginAttempts ( user . foreign . map ( _ . provider ) . getOrElse ( Constant . HostName ) , username )
2017-01-23 00:24:47 +00:00
Empty
2016-01-29 22:03:30 +00:00
}
}
2020-06-22 14:20:39 +00:00
/* *
* This method is belong to AuthUser , it is used for authentication ( Login stuff )
* 1 get the user over connector .
2024-03-19 10:21:51 +00:00
* 2 check whether it is existing in AuthUser table in obp side .
* 3 if not existing , will create new AuthUser .
2020-06-22 14:20:39 +00:00
* @return Return the authUser
*/
2020-07-01 10:03:41 +00:00
def checkExternalUserViaConnector ( username : String , password : String ) : Box [ AuthUser ] = {
Connector . connector . vend . checkExternalUserCredentials ( username , password , None ) match {
2021-03-16 11:55:19 +00:00
case Full ( InboundExternalUser ( aud , exp , iat , iss , sub , azp , email , emailVerified , name , userAuthContexts ) ) =>
2024-06-06 11:16:50 +00:00
val user = findAuthUserByUsernameAndProvider ( sub , iss ) match { // Check if the external user is already created locally
2020-06-22 14:20:39 +00:00
case Full ( user ) if user . validated_? => // Return existing user if found
logger . debug ( "external user already exists locally, using that one" )
2021-03-16 11:55:19 +00:00
userAuthContexts match {
case Some ( authContexts ) => // Write user auth context to the database
UserAuthContextProvider . userAuthContextProvider . vend . createOrUpdateUserAuthContexts ( user . userIdAsString , authContexts )
2021-03-16 07:26:34 +00:00
case None => // Do nothing
}
2020-06-22 14:20:39 +00:00
user
case _ => // If not found, create a new user
// Create AuthUser using fetched data from connector
// assuming that user's email is always validated
logger . debug ( "external user " + sub + " does not exist locally, creating one" )
AuthUser . create
2020-07-01 13:26:05 +00:00
. firstName ( name . getOrElse ( sub ) )
2020-06-22 14:20:39 +00:00
. email ( email . getOrElse ( "" ) )
. username ( sub )
// No need to store password, so store dummy string instead
. password ( generateUUID ( ) )
2021-03-15 16:05:09 +00:00
// TODO add field stating external password check only.
2020-06-22 14:20:39 +00:00
. provider ( iss )
2020-07-03 09:35:03 +00:00
. validated ( emailVerified . exists ( _ . equalsIgnoreCase ( "true" ) ) )
2021-03-24 21:45:38 +00:00
. saveMe ( ) //NOTE, we will create the resourceUser in the `saveMe()` method.
}
userAuthContexts match {
case Some ( authContexts ) => { // Write user auth context to the database
// get resourceUserId from AuthUser.
val resourceUserId = user . user . foreign . map ( _ . userId ) . getOrElse ( "" )
// we try to catch this exception, the createOrUpdateUserAuthContexts can not break the login process.
2024-03-19 10:21:51 +00:00
tryo { UserAuthContextProvider . userAuthContextProvider . vend . createOrUpdateUserAuthContexts ( resourceUserId , authContexts ) }
2021-03-24 21:45:38 +00:00
. openOr ( logger . error ( s" ${ resourceUserId } checkExternalUserViaConnector.createOrUpdateUserAuthContexts throw exception! " ) )
}
case None => // Do nothing
2020-06-22 14:20:39 +00:00
}
Full ( user )
case _ =>
Empty
}
}
2016-01-29 22:03:30 +00:00
2018-12-16 11:09:05 +00:00
def restoreSomeSessions ( ) : Unit = {
activeBrand ( )
}
override protected def capturePreLoginState ( ) : ( ) => Unit = ( ) => { restoreSomeSessions }
2022-08-09 12:19:01 +00:00
/* *
* The LocParams for the menu item for login .
* Overridden in order to add custom error message . Attention : Not calling super will change the default behavior !
*/
override protected def loginMenuLocParams : List [ LocParam [ Unit ] ] = {
If ( notLoggedIn_? _ , ( ) => RedirectResponse ( "/already-logged-in" ) ) : :
Template ( ( ) => wrapIt ( login ) ) : :
Nil
}
2013-06-18 13:03:40 +00:00
//overridden to allow a redirection if login fails
2017-05-31 11:49:31 +00:00
/* *
2024-03-19 10:21:51 +00:00
* Success cases :
2017-05-31 11:49:31 +00:00
* case1 : user validated && user not locked && user . provider from localhost && password correct --> Login in
* case2 : user validated && user not locked && user . provider not localhost && password correct --> Login in
* case3 : user from remote && checked over connector --> Login in
2024-03-19 10:21:51 +00:00
*
2017-05-31 11:49:31 +00:00
* Error cases :
* case1 : user is locked --> UsernameHasBeenLocked
* case2 : user . validated_? --> account.validation.error
* case3 : right username but wrong password --> Invalid Login Credentials
* case4 : wrong username --> Invalid Login Credentials
* case5 : UnKnow error --> UnexpectedErrorDuringLogin
*/
2020-08-31 08:29:12 +00:00
override def login : NodeSeq = {
2023-03-09 07:27:02 +00:00
// This query parameter is specific to ORY Hydra login request
2023-09-11 13:04:48 +00:00
val loginChallenge : Box [ String ] = ObpS . param ( "login_challenge" ) . or ( S . getSessionAttribute ( "login_challenge" ) )
2024-05-13 14:51:41 +00:00
def redirectUri ( user : Box [ ResourceUser ] ) : String = {
val userId = user . map ( _ . userId ) . getOrElse ( "" )
2024-05-14 08:24:33 +00:00
val hashedAgreementTextOfUser =
2024-09-10 11:39:01 +00:00
UserAgreementProvider . userAgreementProvider . vend . getLastUserAgreement ( userId , "terms_and_conditions" )
2024-05-14 08:24:33 +00:00
. map ( _ . agreementHash ) . getOrElse ( HashUtil . Sha256Hash ( "not set" ) )
2024-05-13 14:51:41 +00:00
val agreementText = getWebUiPropsValue ( "webui_terms_and_conditions" , "not set" )
val hashedAgreementText = HashUtil . Sha256Hash ( agreementText )
2024-09-10 08:56:28 +00:00
if ( hashedAgreementTextOfUser == hashedAgreementText ) { // Check terms and conditions
2024-05-14 08:24:33 +00:00
val hashedAgreementTextOfUser =
2024-09-10 11:39:01 +00:00
UserAgreementProvider . userAgreementProvider . vend . getLastUserAgreement ( userId , "privacy_conditions" )
2024-05-14 08:24:33 +00:00
. map ( _ . agreementHash ) . getOrElse ( HashUtil . Sha256Hash ( "not set" ) )
val agreementText = getWebUiPropsValue ( "webui_privacy_policy" , "not set" )
val hashedAgreementText = HashUtil . Sha256Hash ( agreementText )
if ( hashedAgreementTextOfUser == hashedAgreementText ) { // Check privacy policy
loginRedirect . get match {
case Full ( url ) =>
loginRedirect ( Empty )
url
case _ =>
homePage
}
} else {
"/privacy-policy"
2024-05-13 14:51:41 +00:00
}
} else {
"/terms-and-conditions"
2020-06-23 08:49:34 +00:00
}
2024-05-13 14:51:41 +00:00
2020-06-23 08:49:34 +00:00
}
//Check the internal redirect, in case for open redirect issue.
// variable redirect is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
2023-09-13 14:21:02 +00:00
// val currentUrl = ObpS.uriAndQueryString.getOrElse("/")
2020-06-23 08:49:34 +00:00
// AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
2021-12-06 16:29:14 +00:00
def checkInternalRedirectAndLogUserIn ( preLoginState : ( ) => Unit , redirect : String , user : AuthUser ) = {
2020-11-17 11:31:22 +00:00
if ( Helper . isValidInternalRedirectUrl ( redirect ) ) {
2020-06-23 08:49:34 +00:00
logUserIn ( user , ( ) => {
S . notice ( S . ? ( "logged.in" ) )
preLoginState ( )
2021-06-30 12:46:58 +00:00
if ( emailDomainToSpaceMappings . nonEmpty ) {
2021-06-30 23:12:18 +00:00
Future {
tryo { AuthUser . grantEntitlementsToUseDynamicEndpointsInSpaces ( user ) }
2021-12-06 16:29:14 +00:00
. openOr ( logger . error ( s" ${ user } checkInternalRedirectAndLogUserIn.grantEntitlementsToUseDynamicEndpointsInSpaces throw exception! " ) )
2021-06-30 23:12:18 +00:00
} }
2021-06-30 12:46:58 +00:00
if ( emailDomainToEntitlementMappings . nonEmpty ) {
2021-06-30 23:12:18 +00:00
Future {
tryo { AuthUser . grantEmailDomainEntitlementsToUser ( user ) }
2021-12-06 16:29:14 +00:00
. openOr ( logger . error ( s" ${ user } checkInternalRedirectAndLogUserIn.grantEmailDomainEntitlementsToUser throw exception! " ) )
2021-06-30 23:12:18 +00:00
} }
2022-06-06 14:54:06 +00:00
// We use Hydra as an Headless Identity Provider which implies OBP-API must provide User Management.
// If there is the query parameter login_challenge in a url we know it is tha Hydra request
// TODO Write standalone application for Login and Consent Request of Hydra as Identity Provider
integrateWithHydra match {
2022-06-16 09:15:25 +00:00
case true =>
if ( loginChallenge . isEmpty == false ) {
val acceptLoginRequest = new AcceptLoginRequest
val adminApi : AdminApi = new AdminApi
acceptLoginRequest . setSubject ( user . username . get )
val result = adminApi . acceptLoginRequest ( loginChallenge . getOrElse ( "" ) , acceptLoginRequest )
S . redirectTo ( result . getRedirectTo )
} else {
S . redirectTo ( redirect )
}
2022-06-06 14:54:06 +00:00
case false =>
S . redirectTo ( redirect )
}
2020-06-23 08:49:34 +00:00
} )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
logger . info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
}
}
2020-06-23 12:54:16 +00:00
def isObpProvider ( user : AuthUser ) = {
2024-08-12 12:55:44 +00:00
// TODO Consider does http://host should match https://host in development mode
2023-03-03 18:43:22 +00:00
user . getProvider ( ) == Constant . localIdentityProvider
2020-06-23 12:54:16 +00:00
}
def obpUserIsValidatedAndNotLocked ( usernameFromGui : String , user : AuthUser ) = {
2023-01-25 14:04:29 +00:00
user . validated_? && ! LoginAttempt . userIsLocked ( user . getProvider ( ) , usernameFromGui ) &&
2020-06-23 12:54:16 +00:00
isObpProvider ( user )
}
def externalUserIsValidatedAndNotLocked ( usernameFromGui : String , user : AuthUser ) = {
2023-01-25 14:04:29 +00:00
user . validated_? && ! LoginAttempt . userIsLocked ( user . getProvider ( ) , usernameFromGui ) &&
2020-06-23 12:54:16 +00:00
! isObpProvider ( user )
}
2024-03-19 10:21:51 +00:00
2016-12-23 14:30:27 +00:00
def loginAction = {
if ( S . post_? ) {
2023-09-11 13:04:48 +00:00
val usernameFromGui = ObpS . param ( "username" ) . getOrElse ( "" )
val passwordFromGui = ObpS . param ( "password" ) . getOrElse ( "" )
val usernameEmptyField = ObpS . param ( "username" ) . map ( _ . isEmpty ( ) ) . getOrElse ( true )
val passwordEmptyField = ObpS . param ( "password" ) . map ( _ . isEmpty ( ) ) . getOrElse ( true )
2020-05-11 13:05:39 +00:00
val emptyField = usernameEmptyField || passwordEmptyField
emptyField match {
case true =>
2024-03-19 10:21:51 +00:00
if ( usernameEmptyField )
2020-05-11 13:05:39 +00:00
S . error ( "login-form-username-error" , Helper . i18n ( "please.enter.your.username" ) )
2024-03-19 10:21:51 +00:00
if ( passwordEmptyField )
2020-05-11 13:05:39 +00:00
S . error ( "login-form-password-error" , Helper . i18n ( "please.enter.your.password" ) )
case false =>
2024-06-06 11:16:50 +00:00
findAuthUserByUsernameLocallyLegacy ( usernameFromGui ) match {
2020-06-23 12:54:16 +00:00
case Full ( user ) if ! user . validated_? =>
S . error ( S . ? ( "account.validation.error" ) )
2024-03-19 10:21:51 +00:00
2020-06-23 12:54:16 +00:00
// Check if user comes from localhost and
case Full ( user ) if obpUserIsValidatedAndNotLocked ( usernameFromGui , user ) =>
if ( user . testPassword ( Full ( passwordFromGui ) ) ) { // if User is NOT locked and password is good
// Reset any bad attempt
2023-01-25 14:04:29 +00:00
LoginAttempt . resetBadLoginAttempts ( user . getProvider ( ) , usernameFromGui )
2020-05-11 13:05:39 +00:00
val preLoginState = capturePreLoginState ( )
2021-11-24 16:10:47 +00:00
// User init actions
2021-12-06 16:29:14 +00:00
AfterApiAuth . innerLoginUserInitAction ( Full ( user ) )
2020-06-23 08:49:34 +00:00
logger . info ( "login redirect: " + loginRedirect . get )
2024-05-13 14:51:41 +00:00
val redirect = redirectUri ( user . user . foreign )
2021-12-06 16:29:14 +00:00
checkInternalRedirectAndLogUserIn ( preLoginState , redirect , user )
2020-06-23 12:54:16 +00:00
} else { // If user is NOT locked AND password is wrong => increment bad login attempt counter.
2023-01-25 14:04:29 +00:00
LoginAttempt . incrementBadLoginAttempts ( user . getProvider ( ) , usernameFromGui )
2020-06-24 09:40:13 +00:00
S . error ( Helper . i18n ( "invalid.login.credentials" ) )
2020-06-23 12:54:16 +00:00
}
// If user is locked, send the error to GUI
2023-01-25 14:04:29 +00:00
case Full ( user ) if LoginAttempt . userIsLocked ( user . getProvider ( ) , usernameFromGui ) =>
LoginAttempt . incrementBadLoginAttempts ( user . getProvider ( ) , usernameFromGui )
2020-06-23 12:54:16 +00:00
S . error ( S . ? ( ErrorMessages . UsernameHasBeenLocked ) )
2024-07-12 10:54:50 +00:00
loginRedirect ( ObpS . param ( "Referer" ) . or ( S . param ( "Referer" ) ) )
2024-03-19 10:21:51 +00:00
2025-03-06 14:39:55 +00:00
// Check if user came from CBS and
2020-05-11 13:05:39 +00:00
// if User is NOT locked. Then check username and password
// from connector in case they changed on the south-side
2020-06-23 12:54:16 +00:00
case Full ( user ) if externalUserIsValidatedAndNotLocked ( usernameFromGui , user ) && testExternalPassword ( usernameFromGui , passwordFromGui ) =>
2020-05-11 13:05:39 +00:00
// Reset any bad attempts
2023-01-25 14:04:29 +00:00
LoginAttempt . resetBadLoginAttempts ( user . getProvider ( ) , usernameFromGui )
2020-05-11 13:05:39 +00:00
val preLoginState = capturePreLoginState ( )
2020-06-23 08:49:34 +00:00
logger . info ( "login redirect: " + loginRedirect . get )
2024-05-13 14:51:41 +00:00
val redirect = redirectUri ( user . user . foreign )
2025-03-06 14:39:55 +00:00
//This method is used for connector = cbs* || obpjvm*
2020-05-11 13:05:39 +00:00
//It will update the views and createAccountHolder ....
2023-01-25 14:04:29 +00:00
registeredUserHelper ( user . getProvider ( ) , user . username . get )
2021-11-24 16:10:47 +00:00
// User init actions
2021-12-06 16:29:14 +00:00
AfterApiAuth . innerLoginUserInitAction ( Full ( user ) )
checkInternalRedirectAndLogUserIn ( preLoginState , redirect , user )
2023-03-07 10:39:56 +00:00
2024-03-19 10:21:51 +00:00
2023-03-07 10:39:56 +00:00
// Error case:
// the username exist but provider cannot be matched
// It can happen via next scenario:
// - sign up user at some obp-api cluster
// - change a url of the cluster
2024-03-19 10:21:51 +00:00
// - try to log on user at the cluster
2023-03-07 10:39:56 +00:00
case Full ( user ) if ! isObpProvider ( user ) =>
S . error ( S . ? ( s" ${ ErrorMessages . InvalidProviderUrl } Actual: ${ Constant . localIdentityProvider } , Expected: ${ user . provider } " ) )
2024-03-19 10:21:51 +00:00
2020-06-23 12:54:16 +00:00
// If user cannot be found locally, try to authenticate user via connector
2025-03-06 14:39:55 +00:00
case Empty if ( APIUtil . getPropsAsBoolValue ( "connector.user.authentication" , false ) ) =>
2024-03-19 10:21:51 +00:00
2020-06-23 10:54:04 +00:00
val preLoginState = capturePreLoginState ( )
logger . info ( "login redirect: " + loginRedirect . get )
2024-05-13 14:51:41 +00:00
val redirect = redirectUri ( user . foreign )
2020-06-23 10:54:04 +00:00
externalUserHelper ( usernameFromGui , passwordFromGui ) match {
case Full ( user : AuthUser ) =>
2023-01-25 14:04:29 +00:00
LoginAttempt . resetBadLoginAttempts ( user . getProvider ( ) , usernameFromGui )
2021-11-24 16:10:47 +00:00
// User init actions
2021-12-06 16:29:14 +00:00
AfterApiAuth . innerLoginUserInitAction ( Full ( user ) )
checkInternalRedirectAndLogUserIn ( preLoginState , redirect , user )
2020-05-11 13:05:39 +00:00
case _ =>
2023-01-25 14:04:29 +00:00
LoginAttempt . incrementBadLoginAttempts ( user . foreign . map ( _ . provider ) . getOrElse ( Constant . HostName ) , username . get )
2020-05-11 13:05:39 +00:00
Empty
2020-07-01 10:03:41 +00:00
S . error ( Helper . i18n ( "invalid.login.credentials" ) )
2020-06-23 10:54:04 +00:00
}
2024-03-19 10:21:51 +00:00
//If there is NO the username, throw the error message.
case Empty =>
2020-06-24 09:40:13 +00:00
S . error ( Helper . i18n ( "invalid.login.credentials" ) )
2023-03-23 12:15:59 +00:00
case unhandledCase =>
logger . error ( "------------------------------------------------------" )
logger . error ( s" username from GUI: $usernameFromGui " )
logger . error ( "An unexpected login error occurred:" )
logger . error ( unhandledCase )
logger . error ( "------------------------------------------------------" )
2023-01-25 14:04:29 +00:00
LoginAttempt . incrementBadLoginAttempts ( user . foreign . map ( _ . provider ) . getOrElse ( Constant . HostName ) , usernameFromGui )
2020-05-11 13:05:39 +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
}
}
}
2024-03-19 10:21:51 +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 = {
2020-04-26 08:19:05 +00:00
scala . xml . XML . loadString ( loginSubmitButton ( loginButtonText , loginAction _ ) . toString ( ) . replace ( "type=\"submit\"" , "class=\"submit\" type=\"submit\"" ) )
2016-12-23 14:30:27 +00:00
}
2017-08-04 07:00:59 +00:00
val bind =
"submit" # > insertSubmitButton
bind ( loginXhtml )
2013-06-18 13:03:40 +00:00
}
2020-12-09 16:22:59 +00:00
override def logout = {
logoutCurrentUser
S . request match {
case Full ( a ) => a . param ( "redirect" ) match {
case Full ( customRedirect ) => S . redirectTo ( customRedirect )
case _ => S . redirectTo ( homePage )
}
case _ => S . redirectTo ( homePage )
}
}
2024-03-19 10:21:51 +00:00
2017-05-31 11:49:31 +00:00
/* *
2020-06-19 15:16:49 +00:00
* The user authentications is not exciting in obp side , it need get the user via connector
2017-05-31 11:49:31 +00:00
*/
2020-06-23 12:54:16 +00:00
def testExternalPassword ( usernameFromGui : String , passwordFromGui : String ) : Boolean = {
2024-10-17 06:35:29 +00:00
checkExternalUserViaConnector ( usernameFromGui , passwordFromGui ) match {
case Full ( user : AuthUser ) => true
case _ => false
2020-06-23 12:54:16 +00:00
}
2017-02-13 18:01:56 +00:00
}
2024-03-19 10:21:51 +00:00
2017-04-17 18:30:31 +00:00
/* *
* This method will update the views and createAccountHolder . . . .
*/
2017-02-09 13:34:42 +00:00
def externalUserHelper ( name : String , password : String ) : Box [ AuthUser ] = {
2024-10-17 06:35:29 +00:00
for {
user <- checkExternalUserViaConnector ( name , password )
u <- Users . users . vend . getUserByProviderAndUsername ( user . getProvider ( ) , name )
} yield {
user
2024-03-19 10:21:51 +00:00
}
2016-07-27 18:29:14 +00:00
}
2024-03-19 10:21:51 +00:00
2017-04-17 18:30:31 +00:00
/* *
* This method will update the views and createAccountHolder . . . .
*/
2023-01-25 14:04:29 +00:00
def registeredUserHelper ( provider : String , username : String ) = {
2025-03-06 14:39:55 +00:00
if ( connector . startsWith ( "rest_vMar2019" ) ) {
2016-10-26 11:03:14 +00:00
for {
2024-06-07 10:14:23 +00:00
u <- Users . users . vend . getUserByProviderAndUsername ( provider , username )
2022-02-18 14:41:19 +00:00
} yield {
2023-09-08 12:55:58 +00:00
refreshUserLegacy ( u , None )
2022-02-18 14:41:19 +00:00
}
2016-10-26 11:03:14 +00:00
}
}
2021-06-29 12:38:50 +00:00
2021-06-29 15:36:17 +00:00
/* *
2024-03-19 10:21:51 +00:00
* A Space is an alias for the OBP Bank . Each Bank / Space can contain many Dynamic Endpoints . If a User belongs to a Space ,
* the User can use those endpoints but not modify them . If a User creates a Bank ( aka Space ) the user can create
2021-06-30 07:35:57 +00:00
* and modify Dynamic Endpoints and other objects in that Bank / Space .
2021-06-29 15:36:17 +00:00
*
* @return
*/
def mySpaces ( user : AuthUser ) : List [ BankId ] = {
2021-06-29 12:38:50 +00:00
//1st: first check the user is validated
if ( user . validated_? ) {
//userEmail = robert.uk.29@example.com
// 2st get the email domain - `example.com`
2021-06-29 15:36:17 +00:00
val emailDomain = StringUtils . substringAfterLast ( user . email . get , "@" )
2021-06-29 12:38:50 +00:00
//3 return the bankIds
2021-06-30 12:46:58 +00:00
emailDomainToSpaceMappings . collectFirst {
case EmailDomainToSpaceMapping ( `emailDomain` , ids ) => ids . map ( BankId ( _ ) ) ;
2021-06-29 15:36:17 +00:00
} getOrElse Nil
2021-06-29 12:38:50 +00:00
} else {
Nil
}
}
2021-06-29 15:36:17 +00:00
2021-06-30 07:35:57 +00:00
def grantEntitlementsToUseDynamicEndpointsInSpaces ( user : AuthUser ) = {
2021-11-19 09:51:17 +00:00
if ( emailDomainToSpaceMappings . nonEmpty ) {
val createdByProcess = "grantEntitlementsToUseDynamicEndpointsInSpaces"
val userId = user . user . obj . map ( _ . userId ) . getOrElse ( "" )
2021-06-29 15:36:17 +00:00
2021-11-19 09:51:17 +00:00
// user's already auto granted entitlements.
val entitlementsGrantedByThisProcess = Entitlement . entitlement . vend . getEntitlementsByUserId ( userId )
. map ( _ . filter ( role => role . createdByProcess == createdByProcess ) )
. getOrElse ( Nil )
2021-06-29 15:36:17 +00:00
2021-11-19 09:51:17 +00:00
def alreadyHasEntitlement ( role : ApiRole , bankId : String ) : Boolean =
entitlementsGrantedByThisProcess . exists ( entitlement => entitlement . roleName == role . toString ( ) && entitlement . bankId == bankId )
2021-06-29 15:36:17 +00:00
2021-11-19 09:51:17 +00:00
//call mySpaces --> get BankIds --> listOfRolesToUseAllDynamicEndpointsAOneBank (at each bank)--> Grant roles (for each role)
val allCurrentDynamicRoleToBankIdPairs : List [ ( ApiRole , String ) ] = for {
BankId ( bankId ) <- mySpaces ( user : AuthUser )
role <- DynamicEndpointHelper . listOfRolesToUseAllDynamicEndpointsAOneBank ( Some ( bankId ) )
} yield {
if ( ! alreadyHasEntitlement ( role , bankId ) ) {
Entitlement . entitlement . vend . addEntitlement ( bankId , userId , role . toString , createdByProcess )
}
2021-06-29 15:36:17 +00:00
2021-11-19 09:51:17 +00:00
role -> bankId
2021-06-29 15:36:17 +00:00
}
2021-11-19 09:51:17 +00:00
// if user's auto granted entitlement invalid, delete it.
2024-03-19 10:21:51 +00:00
// invalid happens when some dynamic endpoints are removed, so the entitlements linked to the deleted dynamic endpoints are invalid.
2021-11-19 09:51:17 +00:00
for {
grantedEntitlement <- entitlementsGrantedByThisProcess
grantedEntitlementRoleName = grantedEntitlement . roleName
grantedEntitlementBankId = grantedEntitlement . bankId
} {
val isInValidEntitlement = ! allCurrentDynamicRoleToBankIdPairs . exists { roleToBankIdPair =>
val ( role , roleBankId ) = roleToBankIdPair
role . toString ( ) == grantedEntitlementRoleName && roleBankId == grantedEntitlementBankId
}
if ( isInValidEntitlement ) {
Entitlement . entitlement . vend . deleteEntitlement ( Full ( grantedEntitlement ) )
}
2021-06-29 12:38:50 +00:00
}
}
}
2024-03-19 10:21:51 +00:00
2021-06-30 12:46:58 +00:00
def grantEmailDomainEntitlementsToUser ( user : AuthUser ) = {
if ( emailDomainToEntitlementMappings . nonEmpty ) {
val createdByProcess = "grantEmailDomainEntitlementsToUser"
val userId = user . user . obj . map ( _ . userId ) . getOrElse ( "" )
// user's already auto granted entitlements.
val entitlementsGrantedByThisProcess = Entitlement . entitlement . vend . getEntitlementsByUserId ( userId )
. map ( _ . filter ( role => role . createdByProcess == createdByProcess ) )
. getOrElse ( Nil )
def alreadyHasEntitlement ( bankId : String , roleName : String ) : Boolean =
entitlementsGrantedByThisProcess . exists ( entitlement => entitlement . roleName == roleName && entitlement . bankId == bankId )
val allEntitlementsFromCurrentProps : List [ ( String , String ) ] = for {
emailDomainToEntitlementMapping <- emailDomainToEntitlementMappings
domain = emailDomainToEntitlementMapping . domain
entitlement <- emailDomainToEntitlementMapping . entitlements if StringUtils . substringAfterLast ( user . email . get , "@" ) == domain
roleName = entitlement . role_name
roleBankId = entitlement . bank_id
} yield {
if ( ! alreadyHasEntitlement ( roleBankId , roleName ) ) {
Entitlement . entitlement . vend . addEntitlement ( roleBankId , userId , roleName , createdByProcess )
}
roleName -> roleBankId
}
// if user's auto granted entitlement invalid, delete it.
2024-03-19 10:21:51 +00:00
// invalid happens when some dynamic endpoints are removed, so the entitlements linked to the deleted dynamic endpoints are invalid.
2021-06-30 12:46:58 +00:00
for {
grantedEntitlement <- entitlementsGrantedByThisProcess
grantedEntitlementRoleName = grantedEntitlement . roleName
grantedEntitlementBankId = grantedEntitlement . bankId
} {
val isInValidEntitlement = ! allEntitlementsFromCurrentProps . exists { roleNameToBankIdPair =>
val ( roleName , roleBankId ) = roleNameToBankIdPair
roleName == grantedEntitlementRoleName && roleBankId == grantedEntitlementBankId
}
if ( isInValidEntitlement ) {
Entitlement . entitlement . vend . deleteEntitlement ( Full ( grantedEntitlement ) )
}
}
}
}
2024-03-19 10:21:51 +00:00
2017-07-13 13:19:12 +00:00
/* *
2022-02-22 13:12:11 +00:00
* This method is used for onboarding bank customer to OBP .
* 1 st : we will get all the accountsHeld from CBS side .
2022-02-18 14:41:19 +00:00
* 2 rd : we will create the account Holder , view and account accesses .
*/
2022-02-22 13:12:11 +00:00
def refreshUser ( user : User , callContext : Option [ CallContext ] ) = {
2019-04-15 10:51:39 +00:00
for {
2022-12-05 08:40:19 +00:00
( accountsHeld , _ ) <- Connector . connector . vend . getBankAccountsForUser ( user . provider , user . name , callContext ) map {
2019-04-29 11:17:40 +00:00
connectorEmptyResponse ( _ , callContext )
}
2023-09-08 12:55:58 +00:00
_ = logger . debug ( s" --> for user( $user ): AuthUser.refreshUser.accountsHeld : ${ accountsHeld } " )
2024-03-19 10:21:51 +00:00
2023-09-08 12:55:58 +00:00
success = refreshViewsAccountAccessAndHolders ( user , accountsHeld , callContext )
2024-03-19 10:21:51 +00:00
2023-09-08 12:55:58 +00:00
} yield {
success
}
}
2024-03-19 10:21:51 +00:00
2023-09-08 12:55:58 +00:00
@deprecated ( "This return Box, not a future, try to use @refreshUser instead. " , "08-09-2023" )
def refreshUserLegacy ( user : User , callContext : Option [ CallContext ] ) = {
for {
2024-03-19 10:21:51 +00:00
( accountsHeld , _ ) <- Connector . connector . vend . getBankAccountsForUserLegacy ( user . provider , user . name , callContext )
2023-09-08 12:55:58 +00:00
_ = logger . debug ( s" --> for user( $user ): AuthUser.refreshUserLegacy.accountsHeld : ${ accountsHeld } " )
2024-03-19 10:21:51 +00:00
2023-07-03 17:54:37 +00:00
success = refreshViewsAccountAccessAndHolders ( user , accountsHeld , callContext )
2024-03-19 10:21:51 +00:00
2020-06-22 08:14:29 +00:00
} yield {
2023-07-03 17:54:37 +00:00
success
2020-06-22 08:14:29 +00:00
}
2019-04-15 10:51:39 +00:00
}
2017-11-30 12:16:57 +00:00
/* *
* This is a helper method
2022-02-23 11:46:03 +00:00
* create / update / delete the views , accountAccess , accountHolders for OBP get accounts from CBS side .
2021-03-19 15:57:20 +00:00
* This method can only be used by the original user ( account holder ) .
2024-03-19 10:21:51 +00:00
* InboundAccount return many fields , but in this method , we only need bankId , accountId and viewId so far .
2017-11-30 12:16:57 +00:00
*/
2023-07-03 17:54:37 +00:00
def refreshViewsAccountAccessAndHolders ( user : User , accountsHeld : List [ InboundAccount ] , callContext : Option [ CallContext ] ) = {
2022-02-15 16:45:29 +00:00
if ( user . isOriginalUser ) {
2024-03-19 10:21:51 +00:00
//first, we compare the accounts in obp and the accounts in cbs,
2022-02-22 13:12:11 +00:00
val ( _ , privateAccountAccess ) = Views . views . vend . privateViewsUserCanAccess ( user )
val obpAccountAccessBankAccountIds = privateAccountAccess . map ( accountAccess => BankIdAccountId ( BankId ( accountAccess . bank_id . get ) , AccountId ( accountAccess . account_id . get ) ) ) . toSet
2024-03-19 10:21:51 +00:00
2022-12-12 12:40:58 +00:00
// This will return all account held for the user, no mater what the source is.
2022-02-16 10:28:47 +00:00
val userOwnBankAccountIds = AccountHolders . accountHolders . vend . getAccountsHeldByUser ( user )
2022-02-18 14:41:19 +00:00
2022-02-16 10:28:47 +00:00
//The accounts from AccountAccess may contains other users' account info, so here we filter the accounts By account holder, only show the user's own accounts
2022-02-18 14:41:19 +00:00
val obpBankAccountIds = obpAccountAccessBankAccountIds . filter ( bankAccountId => userOwnBankAccountIds . contains ( bankAccountId ) ) . toSet
2024-03-19 10:21:51 +00:00
2022-02-18 14:41:19 +00:00
//The accounts from AccountAccess may contains other users' account info, so here we filter the accounts By account holder, only show the user's own accounts
2022-02-22 13:12:11 +00:00
val cbsBankAccountIds = accountsHeld . map ( account => BankIdAccountId ( BankId ( account . bankId ) , AccountId ( account . accountId ) ) ) . toSet
2024-03-19 10:21:51 +00:00
2022-02-15 16:45:29 +00:00
//cbs removed this accounts, but OBP still contains the data for them, so we need to clean data in OBP side.
2022-02-18 14:41:19 +00:00
val cbsRemovedBankAccountIds = obpBankAccountIds diff cbsBankAccountIds
2024-03-19 10:21:51 +00:00
2022-02-15 16:45:29 +00:00
//cbs has new accounts which are not in obp yet, we need to create new data for these accounts.
2022-02-18 14:41:19 +00:00
val csbNewBankAccountIds = cbsBankAccountIds diff obpBankAccountIds
2022-02-16 10:28:47 +00:00
2022-02-22 13:12:11 +00:00
logger . debug ( "refreshViewsAccountAccessAndHolders.cbsRemovedBankAccountIds-------" + cbsRemovedBankAccountIds )
logger . debug ( "refreshViewsAccountAccessAndHolders.csbNewBankAccountIds-------" + csbNewBankAccountIds )
2022-02-16 10:28:47 +00:00
//1rd remove the deprecated accounts
2022-02-16 10:48:12 +00:00
//TODO. need to double check if we need to clean accountidmapping table, account meta data (MappedTag) ....
2022-02-16 10:28:47 +00:00
for {
2022-02-18 14:41:19 +00:00
cbsRemovedBankAccountId <- cbsRemovedBankAccountIds
2023-07-03 15:32:07 +00:00
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.cbsRemovedBankAccountIds.cbsRemovedBankAccountId: start-------" + cbsRemovedBankAccountId )
2022-02-18 14:41:19 +00:00
bankId = cbsRemovedBankAccountId . bankId
accountId = cbsRemovedBankAccountId . accountId
2023-05-16 12:08:43 +00:00
_ = Views . views . vend . revokeAccountAccessByUser ( bankId , accountId , user , callContext )
2022-02-18 14:41:19 +00:00
_ = AccountHolders . accountHolders . vend . deleteAccountHolder ( user , cbsRemovedBankAccountId )
2022-02-22 13:12:11 +00:00
cbsAccount = accountsHeld . find ( cbsAccount => cbsAccount . bankId == bankId . value && cbsAccount . accountId == accountId . value )
2022-02-18 14:41:19 +00:00
viewId <- cbsAccount . map ( _ . viewsToGenerate ) . getOrElse ( List . empty [ String ] )
2023-06-20 08:32:17 +00:00
_ = UserRefreshes . UserRefreshes . vend . createOrUpdateRefreshUser ( user . userId )
2023-07-03 15:32:07 +00:00
success <- Views . views . vend . removeCustomView ( ViewId ( viewId ) , cbsRemovedBankAccountId )
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.cbsRemovedBankAccountIds.cbsRemovedBankAccountId: finish-------" + cbsRemovedBankAccountId )
2022-02-18 14:41:19 +00:00
} yield {
2023-07-03 15:32:07 +00:00
success
2022-02-16 10:28:47 +00:00
}
2024-03-19 10:21:51 +00:00
2022-02-23 11:46:03 +00:00
//2st: create views/accountAccess/accountHolders for the new coming accounts
2022-02-15 16:45:29 +00:00
for {
2022-02-18 14:41:19 +00:00
newBankAccountId <- csbNewBankAccountIds
2023-07-03 15:32:07 +00:00
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.csbNewBankAccountId.newBankAccountId: start-------" + newBankAccountId )
2022-12-08 23:42:18 +00:00
_ = AccountHolders . accountHolders . vend . getOrCreateAccountHolder ( user , newBankAccountId , Some ( "UserAuthContext" ) )
2022-02-18 14:41:19 +00:00
bankId = newBankAccountId . bankId
accountId = newBankAccountId . accountId
2022-02-22 13:12:11 +00:00
newBankAccount = accountsHeld . find ( cbsAccount => cbsAccount . bankId == bankId . value && cbsAccount . accountId == accountId . value )
2022-02-18 14:41:19 +00:00
viewId <- newBankAccount . map ( _ . viewsToGenerate ) . getOrElse ( List . empty [ String ] )
2023-06-20 02:23:24 +00:00
view <- Views . views . vend . getOrCreateSystemViewFromCbs ( viewId ) //TODO, only support system views so far, may add custom views later.
2023-06-20 08:32:17 +00:00
_ = UserRefreshes . UserRefreshes . vend . createOrUpdateRefreshUser ( user . userId )
2023-07-03 13:05:04 +00:00
view <- if ( view . isSystem ) //if the view is a system view, we will call `grantAccessToSystemView`
Views . views . vend . grantAccessToSystemView ( bankId , accountId , view , user )
else //otherwise, we will call `grantAccessToCustomView`
Views . views . vend . grantAccessToCustomView ( view . uid , user )
2023-07-03 15:32:07 +00:00
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.csbNewBankAccountId.newBankAccountId: finish-------" + newBankAccountId )
2022-02-15 16:45:29 +00:00
} yield {
2023-07-03 13:05:04 +00:00
view
2022-02-15 16:45:29 +00:00
}
2022-02-22 13:12:11 +00:00
2022-10-12 21:07:48 +00:00
//3rd: if the ids are not change, but views are changed, we still need compare the view for each account:
if ( cbsRemovedBankAccountIds . equals ( csbNewBankAccountIds ) ) {
for {
bankAccountId <- obpBankAccountIds
// we can not get the views from the `viewDefinition` table, because we can not delete system views at all. we need to read the view from accountAccess table.
//obpViewsForAccount = MapperViews.availableViewsForAccount(bankAccountId).map(_.viewId.value)
obpViewsForAccount = Views . views . vend . privateViewsUserCanAccessForAccount ( user , bankAccountId ) . map ( _ . viewId . value )
2023-07-03 15:32:07 +00:00
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.obpViewsForAccount-------" + obpViewsForAccount )
2024-03-19 10:21:51 +00:00
2022-10-12 21:07:48 +00:00
cbsViewsForAccount = accountsHeld . find ( account => account . bankId . equals ( bankAccountId . bankId . value ) && account . accountId . equals ( bankAccountId . accountId . value ) ) . map ( _ . viewsToGenerate ) . getOrElse ( Nil )
2023-06-20 02:23:24 +00:00
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.cbsViewsForAccount-------" + cbsViewsForAccount )
2022-10-12 21:07:48 +00:00
//cbs removed these views, but OBP still contains the data for them, so we need to clean data in OBP side.
cbsRemovedViewsForAccount = obpViewsForAccount diff cbsViewsForAccount
2023-06-20 02:23:24 +00:00
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.cbsRemovedViewsForAccount-------" + cbsRemovedViewsForAccount )
2022-10-12 21:07:48 +00:00
_ = if ( cbsRemovedViewsForAccount . nonEmpty ) {
2024-03-22 15:12:34 +00:00
val cbsRemovedBankIdAccountIdViewIds = cbsRemovedViewsForAccount . map ( view => BankIdAccountIdViewId ( bankAccountId . bankId , bankAccountId . accountId , ViewId ( view ) ) )
2024-03-27 13:52:41 +00:00
Views . views . vend . revokeAccessToMultipleViews ( cbsRemovedBankIdAccountIdViewIds , user )
2022-10-12 21:07:48 +00:00
cbsRemovedViewsForAccount . map ( view => Views . views . vend . removeCustomView ( ViewId ( view ) , bankAccountId ) )
UserRefreshes . UserRefreshes . vend . createOrUpdateRefreshUser ( user . userId )
2024-03-19 10:21:51 +00:00
}
2022-10-12 21:07:48 +00:00
//cbs has new views which are not in obp yet, we need to create new data for these accounts.
csbNewViewsForAccount = cbsViewsForAccount diff obpViewsForAccount
2023-06-20 02:23:24 +00:00
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.csbNewViewsForAccount-------" + csbNewViewsForAccount )
2023-07-03 17:54:37 +00:00
success = if ( csbNewViewsForAccount . nonEmpty ) {
2022-10-12 21:07:48 +00:00
for {
newViewForAccount <- csbNewViewsForAccount
2023-07-03 15:32:07 +00:00
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.csbNewViewsForAccount.newViewForAccount start:-------" + newViewForAccount )
2023-06-20 02:23:24 +00:00
view <- Views . views . vend . getOrCreateSystemViewFromCbs ( newViewForAccount ) //TODO, only support system views so far, may add custom views later.
2023-06-20 08:32:17 +00:00
_ = UserRefreshes . UserRefreshes . vend . createOrUpdateRefreshUser ( user . userId )
2024-03-19 10:21:51 +00:00
view <- if ( view . isSystem ) //if the view is a system view, we will call `grantAccessToSystemView`
Views . views . vend . grantAccessToSystemView ( bankAccountId . bankId , bankAccountId . accountId , view , user )
2022-10-12 21:07:48 +00:00
else //otherwise, we will call `grantAccessToCustomView`
Views . views . vend . grantAccessToCustomView ( view . uid , user )
2023-07-03 15:32:07 +00:00
_ = logger . debug ( "refreshViewsAccountAccessAndHolders.csbNewViewsForAccount.newViewForAccount finish:-------" + newViewForAccount )
2023-07-03 13:05:04 +00:00
} yield {
view
2022-10-12 21:07:48 +00:00
}
2024-03-19 10:21:51 +00:00
}
2022-10-12 21:07:48 +00:00
} yield {
2023-07-03 17:54:37 +00:00
success
2022-10-12 21:07:48 +00:00
}
}
2023-07-03 17:54:37 +00:00
true
2024-03-19 10:21:51 +00:00
}
2023-07-03 17:54:37 +00:00
else {
false
}
2017-07-13 13:19:12 +00:00
}
2024-03-18 14:08:13 +00:00
/*
┌────────────┐
│ FIND A USER │
│ AT MAPPER DB │
└──────┬─────┘
___________▽ ___________ ┌────────────────────────┐
╱ props : ╲ │ FIND USER BY COMPOSITE │
╱ local_identity_provider ╲ ______________________│ KEY ( username , │
╲ ╱ yes │ local_identity_provider ) │
╲ _______________________ ╱ └────────────┬───────────┘
│ no │
___▽ ____ ┌────────────────────────┐ │
╱ props : ╲ │ FIND USER BY COMPOSITE │ │
╱ hostname ╲ ___│ KEY ( username , hostname ) │ │
╲ ╱ yes └────────────┬───────────┘ │
╲ ________ ╱ │ │
│ no │ │
┌──▽──┐ │ │
│ ERROR │ │ │
└─────┘ │ │
└──────┬──────────────────┘
┌────▽────┐
│ BOX [ USER ] │
└─────────┘
*/
2017-05-31 11:49:31 +00:00
/* *
2024-03-19 10:21:51 +00:00
* Find the Auth User by the composite key ( username , provider ) .
* Only search at the local database .
* Please note that provider is implicitly defined i . e . not provided via a parameter
2024-03-18 14:08:13 +00:00
*/
2024-06-06 11:16:50 +00:00
@deprecated ( "AuthUser unique key is username and provider, please use @findAuthUserByUsernameAndProvider instead." , "06.06.2024" )
def findAuthUserByUsernameLocallyLegacy ( name : String ) : Box [ TheUserType ] = {
2024-04-15 10:17:10 +00:00
// 1st try is provider with local_identity_provider or hostname value
2024-03-18 14:08:13 +00:00
find ( By ( this . username , name ) , By ( this . provider , Constant . localIdentityProvider ) )
2024-04-15 10:17:10 +00:00
// 2nd try is provider with null value
. or ( find ( By ( this . username , name ) , NullRef ( this . provider ) ) )
// 3rd try is provider with empty string value
. or ( find ( By ( this . username , name ) , By ( this . provider , "" ) ) )
2016-09-05 20:04:16 +00:00
}
2024-06-06 11:16:50 +00:00
def findAuthUserByUsernameAndProvider ( name : String , provider : String ) : Box [ TheUserType ] = {
find ( By ( this . username , name ) , By ( this . provider , provider ) )
}
2024-03-19 10:21:51 +00:00
def findAuthUserByPrimaryKey ( key : Long ) : Box [ TheUserType ] = {
find ( By ( this . user , key ) )
}
2019-09-06 13:42:28 +00:00
def passwordResetUrl ( name : String , email : String , userId : String ) : String = {
find ( By ( this . username , name ) ) match {
case Full ( authUser ) if authUser . validated_? && authUser . email == email =>
Users . users . vend . getUserByUserId ( userId ) match {
case Full ( u ) if u . name == name && u . emailAddress == email =>
authUser . resetUniqueId ( ) . save
2021-12-22 14:39:34 +00:00
val resetLink = Constant . HostName +
2019-09-06 13:42:28 +00:00
passwordResetPath . mkString ( "/" , "/" , "/" ) + urlEncode ( authUser . getUniqueId ( ) )
2019-09-10 14:01:29 +00:00
logger . warn ( s" Password reset url is created for this user: $email " )
2019-09-09 11:51:40 +00:00
// TODO Notify via email appropriate persons
2019-09-06 13:42:28 +00:00
resetLink
case _ => ""
}
case _ => ""
}
}
2021-06-16 22:35:57 +00:00
override def passwordResetXhtml = {
2021-06-17 10:01:56 +00:00
< div id = "recover-password" tabindex = "-1" >
2023-09-13 14:25:11 +00:00
< h1 > { if ( ObpS . queryString . isDefined ) Helper . i18n ( "set.your.password" ) else S . ? ( "reset.your.password" ) } </ h1 >
2023-09-13 15:36:20 +00:00
< form action = { ObpS . uri } method = "post" >
2021-06-17 10:01:56 +00:00
< div class = "form-group" >
< label for = "password" > { S . ? ( "enter.your.new.password" ) } </ label > < span >< input id = "password" class = "form-control" type = "password" /></ span >
</ div >
< div class = "form-group" >
< label for = "repeatpassword" > { S . ? ( "repeat.your.new.password" ) } </ label > < span >< input id = "repeatpassword" class = "form-control" type = "password" /></ span >
</ div >
< div class = "form-group" >
< input type = "submit" class = "btn btn-danger" />
</ div >
</ form >
</ div >
2021-06-16 22:35:57 +00:00
}
2019-04-19 08:59:23 +00:00
/* *
* Find the authUsers by author email ( authUser and resourceUser are the same ) .
* Only search for the local database .
*/
protected def findUsersByEmailLocally ( email : String ) : List [ TheUserType ] = {
val usernames : List [ String ] = this . getResourceUsersByEmail ( email ) . map ( _ . user . name )
findAll ( ByList ( this . username , usernames ) )
}
2022-09-15 11:07:46 +00:00
def signupSubmitButtonValue ( ) = getWebUiPropsValue ( "webui_signup_form_submit_button_value" , S . ? ( "sign.up" ) )
2020-08-31 08:29:12 +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:
2023-09-13 14:21:02 +00:00
// val currentUrl = ObpS.uriAndQueryString.getOrElse("/")
2017-02-20 08:28:26 +00:00
// 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 _ =>
2021-03-11 11:29:40 +00:00
//if the register page url (user_mgt/sign_up?after-signup=link-to-customer) contains the parameter
//after-signup=link-to-customer,then it will redirect to the on boarding customer page.
2023-09-13 15:39:49 +00:00
ObpS . param ( "after-signup" ) match {
case url if ( url . equals ( "link-to-customer" ) ) =>
2021-03-11 11:29:40 +00:00
"/add-user-auth-context-update-request"
case _ =>
homePage
}
2017-02-20 08:28:26 +00:00
}
if ( Helper . isValidInternalRedirectUrl ( redir . toString ) ) {
actionsAfterSignup ( theUser , ( ) => {
S . redirectTo ( redir )
} )
} else {
S . error ( S . ? ( ErrorMessages . InvalidInternalRedirectUrl ) )
2019-09-10 14:01:29 +00:00
logger . info ( ErrorMessages . InvalidInternalRedirectUrl + loginRedirect . get )
2017-02-20 08:28:26 +00:00
}
2014-04-15 08:51:36 +00:00
2015-10-04 18:59:08 +00:00
case xs =>
2020-05-11 13:05:39 +00:00
xs . foreach {
e => S . error ( e . field . uniqueFieldId . openOrThrowException ( "There is no uniqueFieldId." ) , e . msg )
}
2015-10-04 18:59:08 +00:00
signupFunc ( Full ( innerSignup _ ) )
2014-04-15 08:51:36 +00:00
}
}
2017-08-09 19:56:36 +00:00
def innerSignup = {
2022-09-15 11:07:46 +00:00
val bind = "type=submit" # > signupSubmitButton ( signupSubmitButtonValue ( ) , testSignup _ )
2017-08-18 18:26:34 +00:00
bind ( signupXhtml ( theUser ) )
2017-08-09 19:56:36 +00:00
}
2021-08-26 10:53:38 +00:00
if ( APIUtil . getPropsAsBoolValue ( "user_invitation.mandatory" , false ) )
S . redirectTo ( "/user-invitation-info" )
else
innerSignup
2014-04-15 08:51:36 +00:00
}
2021-06-25 14:26:40 +00:00
def scrambleAuthUser ( userPrimaryKey : UserPrimaryKey ) : Box [ Boolean ] = tryo {
AuthUser . find ( By ( AuthUser . user , userPrimaryKey . value ) ) match {
2021-07-20 10:58:27 +00:00
case Full ( user ) =>
2021-07-14 12:55:06 +00:00
val scrambledUser = user . firstName ( Helpers . randomString ( 16 ) )
2021-06-25 14:26:40 +00:00
. email ( Helpers . randomString ( 10 ) + "@example.com" )
2021-07-14 12:55:06 +00:00
. username ( "DELETED-" + Helpers . randomString ( 16 ) )
. firstName ( Helpers . randomString ( 16 ) )
. lastName ( Helpers . randomString ( 16 ) )
2021-06-25 14:26:40 +00:00
. password ( Helpers . randomString ( 40 ) )
. validated ( false )
2023-01-03 15:59:06 +00:00
scrambledUser . save
2021-07-20 10:58:27 +00:00
case Empty => true // There is a resource user but no the correlated Auth user
case _ => false // Error case
2021-06-25 14:26:40 +00:00
}
}
2025-01-08 15:50:48 +00:00
def validateAuthUser ( userPrimaryKey : UserPrimaryKey ) : Box [ AuthUser ] = tryo {
AuthUser . find ( By ( AuthUser . user , userPrimaryKey . value ) ) match {
case Full ( user ) =>
user . validated ( true ) . saveMe ( )
}
}
2021-06-25 14:26:40 +00:00
2016-01-15 17:37:06 +00:00
}