PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/src/main/scala/code/model/dataAccess/AuthUser.scala

https://bitbucket.org/fsbt-tech-api/obp-api
Scala | 829 lines | 594 code | 95 blank | 140 comment | 100 complexity | 8831def15ae0929ea6b50ab71ef30227 MD5 | raw file
  1. /**
  2. Open Bank Project - API
  3. Copyright (C) 2011-2016, TESOBE Ltd
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU Affero General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Affero General Public License for more details.
  12. You should have received a copy of the GNU Affero General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. Email: contact@tesobe.com
  15. TESOBE Ltd
  16. Osloerstrasse 16/17
  17. Berlin 13359, Germany
  18. This product includes software developed at
  19. TESOBE (http://www.tesobe.com/)
  20. by
  21. Simon Redfern : simon AT tesobe DOT com
  22. Stefan Bethge : stefan AT tesobe DOT com
  23. Everett Sochowski : everett AT tesobe DOT com
  24. Ayoub Benali: ayoub AT tesobe DOT com
  25. */
  26. package code.model.dataAccess
  27. import java.util.UUID
  28. import code.api.util.APIUtil.isValidStrongPassword
  29. import code.api.util.{APIUtil, ErrorMessages}
  30. import code.api.{DirectLogin, OAuthHandshake}
  31. import code.bankconnectors.{Connector, InboundUser}
  32. import net.liftweb.common._
  33. import net.liftweb.http._
  34. import net.liftweb.mapper._
  35. import net.liftweb.util.Mailer.{BCC, From, Subject, To}
  36. import net.liftweb.util._
  37. import scala.xml.{NodeSeq, Text}
  38. import code.loginattempts.LoginAttempt
  39. import code.users.Users
  40. import code.util.Helper
  41. import net.liftweb.util
  42. /**
  43. * An O-R mapped "User" class that includes first name, last name, password
  44. *
  45. *
  46. * // TODO Document the difference between this and AuthUser / ResourceUser
  47. *
  48. */
  49. class AuthUser extends MegaProtoUser[AuthUser] with Logger {
  50. def getSingleton = AuthUser // what's the "meta" server
  51. object user extends MappedLongForeignKey(this, ResourceUser)
  52. /**
  53. * The username field for the User.
  54. */
  55. lazy val username: userName = new userName()
  56. class userName extends MappedString(this, 64) {
  57. override def displayName = S.?("username")
  58. override def dbIndexed_? = true
  59. override def validations = valUnique(S.?("unique.username")) _ :: super.validations
  60. override val fieldId = Some(Text("txtUsername"))
  61. }
  62. override lazy val password = new MyPasswordNew
  63. class MyPasswordNew extends MappedPassword(this) {
  64. override def displayName = fieldOwner.passwordDisplayName
  65. private var passwordValue = ""
  66. private var invalidPw = false
  67. private var invalidMsg = ""
  68. override def setFromAny(f: Any): String = {
  69. f match {
  70. case a: Array[String] if (a.length == 2 && a(0) == a(1)) => {
  71. passwordValue = a(0).toString;
  72. if (isValidStrongPassword(passwordValue))
  73. invalidPw = false
  74. else {
  75. invalidPw = true
  76. invalidMsg = S.?(ErrorMessages.InvalidStrongPasswordFormat)
  77. }
  78. this.set(a(0))
  79. }
  80. case l: List[String] if (l.length == 2 && l.head == l(1)) => {
  81. passwordValue = l(0).toString;
  82. if (isValidStrongPassword(passwordValue))
  83. invalidPw = false
  84. else {
  85. invalidPw = true
  86. invalidMsg = S.?(ErrorMessages.InvalidStrongPasswordFormat)
  87. }
  88. this.set(l.head)
  89. }
  90. case _ => {
  91. invalidPw = true;
  92. invalidMsg = S.?("passwords.do.not.match")
  93. }
  94. }
  95. get
  96. }
  97. override def validate: List[FieldError] = {
  98. if (super.validate.nonEmpty) super.validate
  99. else if (!invalidPw && password.get != "*") Nil
  100. else if (invalidPw) List(FieldError(this, Text(invalidMsg)))
  101. else List(FieldError(this, Text(S.?("password.must.be.set"))))
  102. }
  103. }
  104. /**
  105. * The provider field for the User.
  106. */
  107. lazy val provider: userProvider = new userProvider()
  108. class userProvider extends MappedString(this, 64) {
  109. override def displayName = S.?("provider")
  110. override val fieldId = Some(Text("txtProvider"))
  111. }
  112. def getProvider() = {
  113. if(provider.get == null) {
  114. Props.get("hostname","")
  115. } else if ( provider.get == "" || provider.get == Props.get("hostname","") ) {
  116. Props.get("hostname","")
  117. } else {
  118. provider.get
  119. }
  120. }
  121. def createUnsavedResourceUser() : ResourceUser = {
  122. val user = Users.users.vend.createUnsavedResourceUser(getProvider(), Some(username), Some(username), Some(email), None).get
  123. user
  124. }
  125. def getResourceUsersByEmail(userEmail: String) : List[ResourceUser] = {
  126. Users.users.vend.getUserByEmail(userEmail) match {
  127. case Full(userList) => userList
  128. case Empty => List()
  129. }
  130. }
  131. def getResourceUsers(): List[ResourceUser] = {
  132. Users.users.vend.getAllUsers match {
  133. case Full(userList) => userList
  134. case Empty => List()
  135. }
  136. }
  137. def getResourceUserByUsername(username: String) : Box[ResourceUser] = {
  138. Users.users.vend.getUserByUserName(username)
  139. }
  140. override def save(): Boolean = {
  141. if(! (user defined_?)){
  142. info("user reference is null. We will create a ResourceUser")
  143. val resourceUser = createUnsavedResourceUser()
  144. val savedUser = Users.users.vend.saveResourceUser(resourceUser)
  145. user(savedUser) //is this saving resourceUser into a user field?
  146. }
  147. else {
  148. info("user reference is not null. Trying to update the ResourceUser")
  149. Users.users.vend.getResourceUserByResourceUserId(user.get).map{ u =>{
  150. info("API User found ")
  151. u.name_(username)
  152. .email(email)
  153. .providerId(username)
  154. .save
  155. }
  156. }
  157. }
  158. super.save()
  159. }
  160. override def delete_!(): Boolean = {
  161. user.obj.map(u => Users.users.vend.deleteResourceUser(u.id))
  162. super.delete_!
  163. }
  164. // Regex to validate an email address as per W3C recommendations: https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
  165. 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
  166. def isEmailValid(e: String): Boolean = e match{
  167. case null => false
  168. case e if e.trim.isEmpty => false
  169. case e if emailRegex.findFirstMatchIn(e).isDefined => true
  170. case _ => false
  171. }
  172. // Override the validate method of MappedEmail class
  173. // There's no way to override the default emailPattern from MappedEmail object
  174. override lazy val email = new MyEmail(this, 48) {
  175. override def validations = super.validations
  176. override def dbIndexed_? = false
  177. override def validate = if (isEmailValid(i_is_!)) Nil else List(FieldError(this, Text(S.?("invalid.email.address"))))
  178. }
  179. }
  180. /**
  181. * The singleton that has methods for accessing the database
  182. */
  183. object AuthUser extends AuthUser with MetaMegaProtoUser[AuthUser]{
  184. import net.liftweb.util.Helpers._
  185. /**Marking the locked state to show different error message */
  186. val usernameLockedStateCode = Long.MaxValue
  187. val connector = Props.get("connector").openOrThrowException("no connector set")
  188. override def emailFrom = Props.get("mail.users.userinfo.sender.address", "sender-not-set")
  189. override def screenWrap = Full(<lift:surround with="default" at="content"><lift:bind /></lift:surround>)
  190. // define the order fields will appear in forms and output
  191. override def fieldOrder = List(id, firstName, lastName, email, username, password, provider)
  192. override def signupFields = List(firstName, lastName, email, username, password)
  193. // If we want to validate email addresses set this to false
  194. override def skipEmailValidation = Props.getBool("authUser.skipEmailValidation", true)
  195. override def loginXhtml = {
  196. val loginXml = Templates(List("templates-hidden","_login")).map({
  197. "form [action]" #> {S.uri} &
  198. "#loginText * " #> {S.?("log.in")} &
  199. "#usernameText * " #> {S.?("username")} &
  200. "#passwordText * " #> {S.?("password")} &
  201. "autocomplete=off [autocomplete] " #> APIUtil.getAutocompleteValue &
  202. "#recoverPasswordLink * " #> {
  203. "a [href]" #> {lostPasswordPath.mkString("/", "/", "")} &
  204. "a *" #> {S.?("recover.password")}
  205. } &
  206. "#SignUpLink * " #> {
  207. "a [href]" #> {AuthUser.signUpPath.foldLeft("")(_ + "/" + _)} &
  208. "a *" #> {S.?("sign.up")}
  209. }
  210. })
  211. <div>{loginXml getOrElse NodeSeq.Empty}</div>
  212. }
  213. /**
  214. * Find current user
  215. */
  216. def getCurrentUserUsername: String = {
  217. for {
  218. current <- AuthUser.currentUser
  219. username <- tryo{current.username.get}
  220. if (username.nonEmpty)
  221. } yield {
  222. return username
  223. }
  224. for {
  225. current <- OAuthHandshake.getUser
  226. username <- tryo{current.name}
  227. if (username.nonEmpty)
  228. } yield {
  229. return username
  230. }
  231. for {
  232. current <- DirectLogin.getUser
  233. username <- tryo{current.name}
  234. if (username.nonEmpty)
  235. } yield {
  236. return username
  237. }
  238. return ""
  239. }
  240. /**
  241. * Find current ResourceUser_UserId from AuthUser, reference the @getCurrentUserUsername
  242. * This method has no parameters, it depends on different login types:
  243. * AuthUser: AuthUser.currentUser
  244. * OAuthHandshake: OAuthHandshake.getUser
  245. * DirectLogin: DirectLogin.getUser
  246. * to get the current Resourceuser.userId feild.
  247. *
  248. * Note: resourceuser has two ids: id(Long) and userid_(String),
  249. * This method return userid_(String).
  250. */
  251. def getCurrentResourceUserUserId: String = {
  252. for {
  253. current <- AuthUser.currentUser
  254. user <- Users.users.vend.getUserByResourceUserId(current.user.get)
  255. } yield {
  256. return user.userId
  257. }
  258. for {
  259. current <- OAuthHandshake.getUser
  260. userId <- tryo{current.userId}
  261. if (userId.nonEmpty)
  262. } yield {
  263. return userId
  264. }
  265. for {
  266. current <- DirectLogin.getUser
  267. userId <- tryo{current.userId}
  268. if (userId.nonEmpty)
  269. } yield {
  270. return userId
  271. }
  272. return ""
  273. }
  274. /**
  275. * The string that's generated when the user name is not found. By
  276. * default: S.?("email.address.not.found")
  277. * The function is overridden in order to prevent leak of information at password reset page if username / email exists or do not exist.
  278. * 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.
  279. */
  280. override def userNameNotFoundString: String = "Thank you. If we found a matching user, password reset instructions have been sent."
  281. /**
  282. * Overridden to use the hostname set in the props file
  283. */
  284. override def sendPasswordReset(name: String) {
  285. findUserByUsername(name) match {
  286. case Full(user) if user.validated_? =>
  287. user.resetUniqueId().save
  288. val resetLink = Props.get("hostname", "ERROR")+
  289. passwordResetPath.mkString("/", "/", "/")+urlEncode(user.getUniqueId())
  290. Mailer.sendMail(From(emailFrom),Subject(passwordResetEmailSubject),
  291. To(user.getEmail) ::
  292. generateResetEmailBodies(user, resetLink) :::
  293. (bccEmail.toList.map(BCC(_))) :_*)
  294. S.notice(S.?(userNameNotFoundString))
  295. S.redirectTo(homePage)
  296. case Full(user) =>
  297. sendValidationEmail(user)
  298. S.notice(S.?("account.validation.resent"))
  299. S.redirectTo(homePage)
  300. case _ => S.error(userNameNotFoundString)
  301. }
  302. }
  303. override def lostPasswordXhtml = {
  304. <div id="authorizeSection">
  305. <div id="userAccess">
  306. <div class="account account-in-content">
  307. Enter your email address or username and we'll email you a link to reset your password
  308. <form class="forgotPassword" action={S.uri} method="post">
  309. <div class="field username">
  310. <label>Username or email address</label> <user:email />
  311. </div>
  312. <div class="field buttons">
  313. <div class="button button-field">
  314. <user:submit />
  315. </div>
  316. </div>
  317. </form>
  318. </div>
  319. </div>
  320. </div>
  321. }
  322. override def lostPassword = {
  323. bind("user", lostPasswordXhtml,
  324. "email" -> SHtml.text("", sendPasswordReset _),
  325. "submit" -> lostPasswordSubmitButton(S.?("submit")))
  326. }
  327. //override def def passwordResetMailBody(user: TheUserType, resetLink: String): Elem = { }
  328. /**
  329. * Overriden to use the hostname set in the props file
  330. */
  331. override def sendValidationEmail(user: TheUserType) {
  332. val resetLink = Props.get("hostname", "ERROR")+"/"+validateUserPath.mkString("/")+
  333. "/"+urlEncode(user.getUniqueId())
  334. val email: String = user.getEmail
  335. val msgXml = signupMailBody(user, resetLink)
  336. Mailer.sendMail(From(emailFrom),Subject(signupMailSubject),
  337. To(user.getEmail) ::
  338. generateValidationEmailBodies(user, resetLink) :::
  339. (bccEmail.toList.map(BCC(_))) :_* )
  340. }
  341. /**
  342. * Set this to redirect to a certain page after a failed login
  343. */
  344. object failedLoginRedirect extends SessionVar[Box[String]](Empty) {
  345. override lazy val __nameSalt = Helpers.nextFuncName
  346. }
  347. def agreeTerms = {
  348. val url = Props.get("webui_agree_terms_url", "")
  349. if (url.isEmpty) {
  350. s""
  351. } else {
  352. scala.xml.Unparsed(s"""<tr><td colspan="2"><input type="checkbox" id="agree-terms-input" /><span id="agree-terms-text">I hereby agree to the <a href="$url" title="T &amp; C">Terms and Conditions</a></span></td></tr>""")
  353. }
  354. }
  355. override def signupXhtml (user:AuthUser) = {
  356. <div id="authorizeSection" class="signupSection">
  357. <div class="signup-error"><span class="lift:Msg?id=signup"/></div>
  358. <div>
  359. <form id="signupForm" method="post" action={S.uri}>
  360. <table>
  361. <tr>
  362. <td colspan="2">{ S.?("sign.up") }</td>
  363. </tr>
  364. {localForm(user, false, signupFields)}
  365. {agreeTerms}
  366. <tr>
  367. <td>&nbsp;</td>
  368. <td><user:submit/></td>
  369. </tr>
  370. </table>
  371. </form>
  372. </div>
  373. </div>
  374. }
  375. def userLoginFailed = {
  376. info("failed: " + failedLoginRedirect.get)
  377. // variable redir is from failedLoginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
  378. // val currentUrl = S.uriAndQueryString.getOrElse("/")
  379. // AuthUser.failedLoginRedirect.set(Full(Helpers.appendParams(currentUrl, List((FailedLoginParam, "true")))))
  380. val redir = failedLoginRedirect.get
  381. //Check the internal redirect, in case for open redirect issue.
  382. // variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
  383. // val currentUrl = S.uriAndQueryString.getOrElse("/")
  384. // AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
  385. if (Helper.isValidInternalRedirectUrl(redir.toString)) {
  386. S.redirectTo(redir.toString)
  387. } else {
  388. S.error(S.?(ErrorMessages.InvalidInternalRedirectUrl))
  389. info(ErrorMessages.InvalidInternalRedirectUrl + loginRedirect.get)
  390. }
  391. S.error("login", S.?("Invalid Username or Password"))
  392. }
  393. def getResourceUserId(username: String, password: String): Box[Long] = {
  394. findUserByUsername(username) match {
  395. case Full(user) if (user.getProvider() == Props.get("hostname","")) =>
  396. if (
  397. user.validated_? &&
  398. // User is NOT locked AND the password is good
  399. ! LoginAttempt.userIsLocked(username) &&
  400. user.testPassword(Full(password)))
  401. {
  402. // We logged in correctly, so reset badLoginAttempts counter (if it exists)
  403. LoginAttempt.resetBadLoginAttempts(username)
  404. Full(user.user) // Return the user.
  405. }
  406. // User is unlocked AND password is bad
  407. else if (
  408. user.validated_? &&
  409. ! LoginAttempt.userIsLocked(username) &&
  410. ! user.testPassword(Full(password))
  411. ) {
  412. LoginAttempt.incrementBadLoginAttempts(username)
  413. Empty
  414. }
  415. // User is locked
  416. else if (LoginAttempt.userIsLocked(username))
  417. {
  418. LoginAttempt.incrementBadLoginAttempts(username)
  419. info(ErrorMessages.UsernameHasBeenLocked)
  420. //TODO need to fix, use Failure instead, it is used to show the error message to the GUI
  421. Full(usernameLockedStateCode)
  422. }
  423. else {
  424. // Nothing worked, so just increment bad login attempts
  425. LoginAttempt.incrementBadLoginAttempts(username)
  426. Empty
  427. }
  428. case Full(user) if (user.getProvider() != Props.get("hostname","")) =>
  429. connector match {
  430. case Helper.matchAnyKafka() if ( Props.getBool("kafka.user.authentication", false) &&
  431. ! LoginAttempt.userIsLocked(username) ) =>
  432. val userId = for { kafkaUser <- getUserFromConnector(username, password)
  433. kafkaUserId <- tryo{kafkaUser.user} } yield {
  434. LoginAttempt.resetBadLoginAttempts(username)
  435. kafkaUserId.toLong
  436. }
  437. userId match {
  438. case Full(l:Long) => Full(l)
  439. case _ =>
  440. LoginAttempt.incrementBadLoginAttempts(username)
  441. Empty
  442. }
  443. case "obpjvm" if ( Props.getBool("obpjvm.user.authentication", false) &&
  444. ! LoginAttempt.userIsLocked(username) ) =>
  445. val userId = for { obpjvmUser <- getUserFromConnector(username, password)
  446. obpjvmUserId <- tryo{obpjvmUser.user} } yield {
  447. LoginAttempt.resetBadLoginAttempts(username)
  448. obpjvmUserId.toLong
  449. }
  450. userId match {
  451. case Full(l:Long) => Full(l)
  452. case _ =>
  453. LoginAttempt.incrementBadLoginAttempts(username)
  454. Empty
  455. }
  456. case _ =>
  457. LoginAttempt.incrementBadLoginAttempts(username)
  458. Empty
  459. }
  460. case _ =>
  461. LoginAttempt.incrementBadLoginAttempts(username)
  462. Empty
  463. }
  464. }
  465. def getUserFromConnector(name: String, password: String):Box[AuthUser] = {
  466. Connector.connector.vend.getUser(name, password) match {
  467. case Full(InboundUser(extEmail, extPassword, extUsername)) => {
  468. info("external user authenticated. login redir: " + loginRedirect.get)
  469. val redir = loginRedirect.get match {
  470. case Full(url) =>
  471. loginRedirect(Empty)
  472. url
  473. case _ =>
  474. homePage
  475. }
  476. val extProvider = connector
  477. val user = findUserByUsername(name) match {
  478. // Check if the external user is already created locally
  479. case Full(user) if user.validated_?
  480. // && user.provider == extProvider
  481. => {
  482. // Return existing user if found
  483. info("external user already exists locally, using that one")
  484. user
  485. }
  486. // If not found, create new user
  487. case _ => {
  488. // Create AuthUser using fetched data from Kafka
  489. // assuming that user's email is always validated
  490. info("external user "+ extEmail +" does not exist locally, creating one")
  491. val newUser = AuthUser.create
  492. .firstName(extUsername)
  493. .email(extEmail)
  494. .username(extUsername)
  495. // No need to store password, so store dummy string instead
  496. .password(UUID.randomUUID().toString)
  497. .provider(extProvider)
  498. .validated(true)
  499. // Save the user in order to be able to log in
  500. newUser.save()
  501. // Return created user
  502. newUser
  503. }
  504. }
  505. Full(user)
  506. }
  507. case _ => {
  508. Empty
  509. }
  510. }
  511. }
  512. //overridden to allow a redirection if login fails
  513. override def login = {
  514. def loginAction = {
  515. if (S.post_?) {
  516. val usernameFromGui = S.param("username").getOrElse("")
  517. val passwordFromGui = S.param("password").getOrElse("")
  518. findUserByUsername(usernameFromGui) match {
  519. // Check if user came from localhost and
  520. // if User is NOT locked and password is good
  521. case Full(user) if user.validated_? &&
  522. user.getProvider() == Props.get("hostname","") &&
  523. ! LoginAttempt.userIsLocked(usernameFromGui) &&
  524. user.testPassword(Full(passwordFromGui)) => {
  525. // Reset any bad attempts
  526. LoginAttempt.resetBadLoginAttempts(usernameFromGui)
  527. val preLoginState = capturePreLoginState()
  528. info("login redir: " + loginRedirect.get)
  529. val redir = loginRedirect.get match {
  530. case Full(url) =>
  531. loginRedirect(Empty)
  532. url
  533. case _ =>
  534. homePage
  535. }
  536. registeredUserHelper(user.username)
  537. //Check the internal redirect, in case for open redirect issue.
  538. // variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
  539. // val currentUrl = S.uriAndQueryString.getOrElse("/")
  540. // AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
  541. if (Helper.isValidInternalRedirectUrl(redir.toString)) {
  542. logUserIn(user, () => {
  543. S.notice(S.?("logged.in"))
  544. preLoginState()
  545. S.redirectTo(redir)
  546. })
  547. } else {
  548. S.error(S.?(ErrorMessages.InvalidInternalRedirectUrl))
  549. info(ErrorMessages.InvalidInternalRedirectUrl + loginRedirect.get)
  550. }
  551. }
  552. // Check if user came from kafka/obpjvm and
  553. // if User is NOT locked. Then check username and password
  554. // from connector in case they changed on the south-side
  555. case Full(user) if user.validated_? &&
  556. user.getProvider() != Props.get("hostname","") &&
  557. ! LoginAttempt.userIsLocked(usernameFromGui) &&
  558. testExternalPassword(Full(user.username.get), Full(passwordFromGui)).getOrElse(false) => {
  559. // Reset any bad attempts
  560. LoginAttempt.resetBadLoginAttempts(usernameFromGui)
  561. val preLoginState = capturePreLoginState()
  562. info("login redir: " + loginRedirect.get)
  563. val redir = loginRedirect.get match {
  564. case Full(url) =>
  565. loginRedirect(Empty)
  566. url
  567. case _ =>
  568. homePage
  569. }
  570. registeredUserHelper(user.username)
  571. //Check the internal redirect, in case for open redirect issue.
  572. // variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
  573. // val currentUrl = S.uriAndQueryString.getOrElse("/")
  574. // AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
  575. if (Helper.isValidInternalRedirectUrl(redir.toString)) {
  576. logUserIn(user, () => {
  577. S.notice(S.?("logged.in"))
  578. preLoginState()
  579. S.redirectTo(redir)
  580. })
  581. } else {
  582. S.error(S.?(ErrorMessages.InvalidInternalRedirectUrl))
  583. info(ErrorMessages.InvalidInternalRedirectUrl + loginRedirect.get)
  584. }
  585. }
  586. // If user is unlocked AND bad password, increment bad login attempt counter.
  587. case Full(user) if user.validated_? &&
  588. user.getProvider() == Props.get("hostname","") &&
  589. ! LoginAttempt.userIsLocked(usernameFromGui) &&
  590. ! user.testPassword(Full(passwordFromGui)) =>
  591. LoginAttempt.incrementBadLoginAttempts(usernameFromGui)
  592. S.error(S.?("Invalid Login Credentials")) // TODO constant / i18n for this string
  593. // If user is locked, send the error to GUI
  594. case Full(user) if LoginAttempt.userIsLocked(usernameFromGui) =>
  595. LoginAttempt.incrementBadLoginAttempts(usernameFromGui)
  596. S.error(S.?(ErrorMessages.UsernameHasBeenLocked))
  597. case Full(user) if !user.validated_? =>
  598. S.error(S.?("account.validation.error")) // Note: This does not seem to get hit when user is not validated.
  599. // If not found locally, try to authenticate user via Kafka, if enabled in props
  600. case Empty if (connector.startsWith("kafka") || connector == "obpjvm") &&
  601. (Props.getBool("kafka.user.authentication", false) ||
  602. Props.getBool("obpjvm.user.authentication", false)) =>
  603. val preLoginState = capturePreLoginState()
  604. info("login redir: " + loginRedirect.get)
  605. val redir = loginRedirect.get match {
  606. case Full(url) =>
  607. loginRedirect(Empty)
  608. url
  609. case _ =>
  610. homePage
  611. }
  612. for {
  613. user_ <- externalUserHelper(usernameFromGui, passwordFromGui)
  614. } yield {
  615. user_
  616. } match {
  617. case u:AuthUser =>
  618. LoginAttempt.resetBadLoginAttempts(usernameFromGui)
  619. //Check the internal redirect, in case for open redirect issue.
  620. // variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
  621. // val currentUrl = S.uriAndQueryString.getOrElse("/")
  622. // AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
  623. if (Helper.isValidInternalRedirectUrl(redir.toString)) {
  624. logUserIn(u, () => {
  625. S.notice(S.?("logged.in"))
  626. preLoginState()
  627. S.redirectTo(redir)
  628. })
  629. } else {
  630. S.error(S.?(ErrorMessages.InvalidInternalRedirectUrl))
  631. info(ErrorMessages.InvalidInternalRedirectUrl + loginRedirect.get)
  632. }
  633. case _ =>
  634. LoginAttempt.incrementBadLoginAttempts(username)
  635. Empty
  636. }
  637. case _ =>
  638. LoginAttempt.incrementBadLoginAttempts(usernameFromGui)
  639. S.error(S.?(ErrorMessages.UnexpectedErrorDuringLogin)) // Note we hit this if user has not clicked email validation link
  640. }
  641. }
  642. }
  643. // In this function we bind submit button to loginAction function.
  644. // In case that unique token of submit button cannot be paired submit action will be omitted.
  645. // Implemented in order to prevent a CSRF attack
  646. def insertSubmitButton = {
  647. scala.xml.XML.loadString(loginSubmitButton(S.?("Login"), loginAction _).toString().replace("type=\"submit\"","class=\"submit\" type=\"submit\""))
  648. }
  649. bind("user", loginXhtml,
  650. "submit" -> insertSubmitButton)
  651. }
  652. def testExternalPassword(usernameFromGui: Box[String], passwordFromGui: Box[String]): Box[Boolean] = {
  653. if (connector.startsWith("kafka") || connector == "obpjvm") {
  654. val res = for {
  655. username <- usernameFromGui
  656. password <- passwordFromGui
  657. user <- getUserFromConnector(username, password)
  658. } yield user match {
  659. case user:AuthUser => true
  660. case _ => false
  661. }
  662. res
  663. } else Empty
  664. }
  665. def externalUserHelper(name: String, password: String): Box[AuthUser] = {
  666. if (connector.startsWith("kafka") || connector == "obpjvm") {
  667. for {
  668. user <- getUserFromConnector(name, password)
  669. u <- Users.users.vend.getUserByUserName(username)
  670. v <- tryo {Connector.connector.vend.updateUserAccountViews(u)}
  671. } yield {
  672. user
  673. }
  674. } else Empty
  675. }
  676. def registeredUserHelper(username: String) = {
  677. if (connector.startsWith("kafka") || connector == "obpjvm") {
  678. for {
  679. u <- Users.users.vend.getUserByUserName(username)
  680. v <- tryo {Connector.connector.vend.updateUserAccountViews(u)}
  681. } yield v
  682. }
  683. }
  684. protected def findUserByUsername(name: String): Box[TheUserType] = {
  685. find(By(this.username, name))
  686. }
  687. //overridden to allow redirect to loginRedirect after signup. This is mostly to allow
  688. // loginFirst menu items to work if the user doesn't have an account. Without this,
  689. // if a user tries to access a logged-in only page, and then signs up, they don't get redirected
  690. // back to the proper page.
  691. override def signup = {
  692. val theUser: TheUserType = mutateUserOnSignup(createNewUserInstance())
  693. val theName = signUpPath.mkString("")
  694. //Check the internal redirect, in case for open redirect issue.
  695. // variable redir is from loginRedirect, it is set-up in OAuthAuthorisation.scala as following code:
  696. // val currentUrl = S.uriAndQueryString.getOrElse("/")
  697. // AuthUser.loginRedirect.set(Full(Helpers.appendParams(currentUrl, List((LogUserOutParam, "false")))))
  698. val loginRedirectSave = loginRedirect.is
  699. def testSignup() {
  700. validateSignup(theUser) match {
  701. case Nil =>
  702. //here we check loginRedirectSave (different from implementation in super class)
  703. val redir = loginRedirectSave match {
  704. case Full(url) =>
  705. loginRedirect(Empty)
  706. url
  707. case _ =>
  708. homePage
  709. }
  710. if (Helper.isValidInternalRedirectUrl(redir.toString)) {
  711. actionsAfterSignup(theUser, () => {
  712. S.redirectTo(redir)
  713. })
  714. } else {
  715. S.error(S.?(ErrorMessages.InvalidInternalRedirectUrl))
  716. info(ErrorMessages.InvalidInternalRedirectUrl + loginRedirect.get)
  717. }
  718. case xs =>
  719. xs.foreach(e => S.error("signup", e.msg))
  720. signupFunc(Full(innerSignup _))
  721. }
  722. }
  723. def innerSignup = bind("user",
  724. signupXhtml(theUser),
  725. "submit" -> signupSubmitButton(S.?("sign.up"), testSignup _))
  726. innerSignup
  727. }
  728. }