Advertisement
Guest User

Untitled

a guest
Jun 9th, 2017
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.28 KB | None | 0 0
  1. package clear.dao
  2.  
  3. import clear.api.intf.{AuthImportFacility, ReportType}
  4. import clear.api.intf.frontend.ChangePasswordResult
  5. import clear.api.intf.frontend.ProviderFrontend
  6.  
  7. import clear.db._
  8.  
  9. import UserType._
  10.  
  11. import scalaz._
  12. import Scalaz._
  13.  
  14. import slick.driver.MySQLDriver.simple._
  15.  
  16. import org.apache.commons.codec.binary.Base64._
  17. import org.apache.commons.codec.binary.Hex._
  18. import org.mindrot.jbcrypt.BCrypt
  19.  
  20. import java.sql.Timestamp
  21. import java.time.Instant
  22.  
  23. case class UserDao(db:Adapter) extends AbstractUserDao {
  24. // Tables
  25. val user = TableQuery[TblUser]
  26. val userFeature = TableQuery[TblUserFeature]
  27. val userAccess = TableQuery[VwUserAccess]
  28. val workItem = TableQuery[TblWorkItem]
  29. val facility = TableQuery[TblFacility]
  30. val pubKey = TableQuery[TblUserPubKey]
  31. val reports = TableQuery[TblWorkItemReport]
  32. val changePassword = TableQuery[TblChangePassword]
  33. val provider = TableQuery[TblProvider]
  34.  
  35. val rng = new java.security.SecureRandom
  36. val dxIcd10New = TableQuery[NewVwIcd10CmNew]
  37. val dxIcd10PcsNew = TableQuery[VwIcd10PcsNew]
  38.  
  39. val CHANGE_PASSWORD_TOKEN_BYTES = 8
  40.  
  41. /**
  42. * Generate a bunch of random hex digits.
  43. */
  44. private def randHexToken(numBytes:Int):String = {
  45. val rnd = Array.fill(numBytes)(0:Byte)
  46. rng.nextBytes(rnd)
  47. encodeHexString(rnd)
  48. }
  49.  
  50. override def getEffectiveDates: List[String] =
  51. db.connect.withSession { implicit session =>
  52. val icd10 = dxIcd10New.map(_.effective_date).list //.groupBy(_.effective_date).list.map(_._1)
  53. val icd10Pcs = dxIcd10PcsNew.map(_.effective_date).list
  54. (icd10 ++ icd10Pcs).sorted.distinct
  55. }
  56.  
  57. override def updatePass(id: Int, newHash: String): Unit =
  58. db.connect.withSession { implicit session =>
  59. user.filter(_.user_uid === id)
  60. .map(_.user_password)
  61. .update(newHash)
  62. }
  63.  
  64. override def findUser(email: String): Option[User] =
  65. db.connect.withSession { implicit session =>
  66. user.filter(u => u.user_email_address === email && u.user_active)
  67. .firstOption
  68. }
  69.  
  70. override def findUserProvider(id: Int): Option[ProviderFrontend] =
  71. db.connect.withSession { implicit session =>
  72. provider.filter(_.user_uid === id)
  73. .map(p => (p.provider_uid,p.provider_lov,p.provider_email,p.display_name, p.type_id))
  74. .firstOption
  75. .map((ProviderFrontend.apply _).tupled)
  76. }
  77.  
  78. override def featuresForUser(userId: Int): List[String] =
  79. db.connect.withSession { implicit session =>
  80. val liveCode =
  81. (for {
  82. me <- userAccess if me.user_uid === userId
  83. scribe <- userAccess if scribe.work_item_uid === me.work_item_uid &&
  84. scribe.user_type === ENCOUNTER_CREATE
  85. } yield scribe).exists.run
  86.  
  87. userFeature.filter(_.user_uid === userId)
  88. .map(_.feature_id)
  89. .list ++
  90. (liveCode ?? List("LIVECODE")) // Include this if any of this user's work items have a LiveCode scribe.
  91. }
  92.  
  93. override def workItemRoles(userId: Int): Map[String,List[AuthImportFacility]] =
  94. db.connect.withSession { implicit session =>
  95. (for {
  96. ua <- userAccess if ua.user_uid === userId
  97. wi <- workItem if wi.work_item_uid === ua.work_item_uid
  98. fac <- facility if fac.facility_id === wi.facility_id
  99. } yield (ua.user_type, fac.client_disposition_name, wi.work_item_uid, fac.timezone)
  100. ).list
  101. .groupBy(_._1)
  102. .map{ case (userType,workItems) => // note that we can't just use mapValues here, as it is non-strict (DB session escapes this scope)
  103. (userType -> workItems.map {
  104. case (`REPORT`, facId, workItemId, tz) =>
  105. AuthImportFacility(facId, workItemId, tz,
  106. reports.filter(_.work_item_uid === workItemId)
  107. .map(r => (r.report_id,r.extension))
  108. .list
  109. .collect{ // Any key that can't map to a ReportType will be skipped
  110. case (ReportType(reportType),ext) => (reportType,ext)
  111. } )
  112. case (_, facId, workItemId, tz) =>
  113. AuthImportFacility(facId, workItemId, tz, Nil)
  114. } )
  115. }
  116. }
  117.  
  118. // FIXME: This will throw on bad Base64
  119. override def getRsaPublicKeys(username: String): List[Array[Byte]] =
  120. db.connect.withSession { implicit session =>
  121. (for {
  122. u <- user if u.user_email_address === username
  123. k <- pubKey if k.user_uid === u.user_uid
  124. } yield k.public_key
  125. ).list.map(decodeBase64(_))
  126. }
  127.  
  128. override def authorizePasswordChange(email: String): Option[String] =
  129. db.connect.withTransaction { implicit session =>
  130. val userForEmail = user.filter(_.user_email_address === email).firstOption
  131. userForEmail.map{
  132. case userInfo => {
  133. // TODO: Possibly do some authorization checks. In the most common case,
  134. // a user won't be logged in, exactly because he doesn't remember his password.
  135. // But we might want to deny this feature to users who are already logged in,
  136. // or against certain types of users (admins, etc.).
  137.  
  138. // Remove any existing change-password-authorizations for this user
  139. changePassword.filter(existingUser => existingUser.user_uid === userInfo.id).delete
  140.  
  141. // Add a new row to the changePassword table, authorizing anyone with the
  142. // newly created token to set the password for the user. We're using
  143. // hex for the token instead of base64 or whatever, so that we don't have
  144. // to worry about conflict with URL characters.
  145. val expTimeSec = 12 * 60 * 60 // 12 hours
  146. val requestToken = randHexToken(CHANGE_PASSWORD_TOKEN_BYTES)
  147. val expDate = Timestamp.from(Instant.now.plusSeconds(expTimeSec))
  148. changePassword += (0, userInfo.id, requestToken, expDate)
  149.  
  150. // Return the request token
  151. requestToken
  152. }
  153. }
  154. }
  155.  
  156. // TODO: Probably can do better than strings for the return status
  157. override def changePasswordByEmail(email: String, authorizationCode: String, newPassword: String): ChangePasswordResult =
  158. db.connect.withTransaction { implicit session =>
  159. val userIdOpt = user.filter(_.user_email_address === email).firstOption.map{u: User => u.id}
  160. val authRecOpt:Option[(Int,Int,String,Timestamp)] = userIdOpt.map{
  161. case userId => changePassword.filter(r => r.user_uid===userId && r.request_token===authorizationCode)
  162. .firstOption
  163. }.flatten
  164.  
  165. authRecOpt match {
  166. case Some((id, user_uid, request_token, expires_utc)) => {
  167. if ( expires_utc.toInstant.compareTo(Instant.now)<=0 ) {
  168. ChangePasswordResult.AuthExpired
  169. } else {
  170. // Hash the new password and stuff it in the DB.
  171. val pwHash = BCrypt.hashpw(newPassword, BCrypt.gensalt)
  172. user.filter(_.user_uid === user_uid).map(u => (u.user_password)).update(pwHash)
  173.  
  174. // Delete the old password change authorization record.
  175. changePassword.filter(_.user_uid===user_uid).delete
  176.  
  177. // TODO: Add some sort of audit trail record
  178.  
  179. ChangePasswordResult.Success
  180. }
  181. }
  182. case None => ChangePasswordResult.BadUserOrToken
  183. }
  184. }
  185. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement