1 /*
2  *  Make.org Core API
3  *  Copyright (C) 2018 Make.org
4  *
5  * This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  *
18  */
19 
20 package org.make.core.user
21 
22 import com.github.plokhotnyuk.jsoniter_scala.core._
23 import enumeratum.values.{StringCirceEnum, StringEnum, StringEnumEntry}
24 import io.circe._
25 import io.circe.generic.semiauto._
26 import io.circe.parser.decode
27 import io.circe.syntax.EncoderOps
28 import io.swagger.annotations.ApiModelProperty
29 import org.make.core._
30 import org.make.core.jsoniter.JsoniterEnum
31 import org.make.core.profile.Profile
32 import org.make.core.question.QuestionId
33 import org.make.core.reference.{Country, Language}
34 import org.make.core.technical.enumeratum.FallbackingCirceEnum.FallbackingStringCirceEnum
35 import org.make.core.user.UserType.{
36   UserTypeAnonymous,
37   UserTypeExternal,
38   UserTypeOrganisation,
39   UserTypePersonality,
40   UserTypeUser,
41   UserTypeVirtual
42 }
43 import scalikejdbc.Binders
44 import spray.json.JsonFormat
45 
46 import java.time.ZonedDateTime
47 import scala.annotation.meta.field
48 
49 sealed abstract class Role(val value: String) extends StringEnumEntry with Product with Serializable
50 
51 object Role extends StringEnum[Role] with FallbackingStringCirceEnum[Role] {
52 
53   override def default(value: String): Role = CustomRole(value)
54 
55   case object RoleSuperAdmin extends Role("ROLE_SUPER_ADMIN")
56   case object RoleAdmin extends Role("ROLE_ADMIN")
57   case object RoleOperator extends Role("ROLE_OPERATOR")
58   case object RoleModerator extends Role("ROLE_MODERATOR")
59   case object RoleDialogueModerator extends Role("ROLE_DIALOGUE_MODERATOR")
60   case object RolePolitical extends Role("ROLE_POLITICAL")
61   case object RoleCitizen extends Role("ROLE_CITIZEN")
62   case object RoleActor extends Role("ROLE_ACTOR")
63   case object RolePanoramicAdmin extends Role("ROLE_PANORAMIC_ADMIN")
64   case object RolePanoramicEditor extends Role("ROLE_PANORAMIC_EDITOR")
65   case object RolePanoramicService extends Role("ROLE_PANORAMIC_SERVICE")
66   case object RolePanoramicData extends Role("ROLE_PANORAMIC_DATA")
67 
68   override val values: IndexedSeq[Role] = findValues
69   final val swaggerAllowableValues =
70     "ROLE_SUPER_ADMIN,ROLE_ADMIN,ROLE_OPERATOR,ROLE_MODERATOR,ROLE_DIALOGUE_MODERATOR,ROLE_POLITICAL,ROLE_CITIZEN,ROLE_ACTOR,ROLE_PANORAMIC_ADMIN,ROLE_PANORAMIC_EDITOR,ROLE_PANORAMIC_SERVICE,ROLE_PANORAMIC_DATA,custom"
71 }
72 
73 final case class CustomRole(override val value: String) extends Role(value)
74 
75 sealed abstract class UserType(val value: String) extends StringEnumEntry with Product with Serializable
76 
77 object UserType extends StringEnum[UserType] with FallbackingStringCirceEnum[UserType] with JsoniterEnum[UserType] {
78 
79   override def default(value: String): UserType = UserTypeUser
80 
81   case object UserTypeAnonymous extends UserType("ANONYMOUS")
82   case object UserTypeUser extends UserType("USER")
83   case object UserTypeOrganisation extends UserType("ORGANISATION")
84   case object UserTypePersonality extends UserType("PERSONALITY")
85   case object UserTypeVirtual extends UserType("VIRTUAL")
86   case object UserTypeExternal extends UserType("EXTERNAL")
87 
88   override def values: IndexedSeq[UserType] = findValues
89   final val swaggerAllowableValues = "ANONYMOUS,USER,ORGANISATION,PERSONALITY,VIRTUAL,EXTERNAL"
90 
91   implicit class UserTypeOps[T](val t: T) extends AnyVal {
92     def isB2B(implicit h: HasUserType[T]): Boolean =
93       Set(UserType.UserTypePersonality, UserType.UserTypeOrganisation).contains(h.userType(t))
94     def isB2C(implicit h: HasUserType[T]): Boolean = h.userType(t) == UserType.UserTypeUser
95   }
96 }
97 
98 trait HasUserType[T] {
99   def userType(t: T): UserType
100 }
101 
102 object HasUserType {
103   implicit val userUserType: HasUserType[User] = _.userType
104 }
105 
106 final case class MailingErrorLog(error: String, date: ZonedDateTime)
107 
108 final case class User(
109   userId: UserId,
110   email: String,
111   firstName: Option[String],
112   lastName: Option[String],
113   lastIp: Option[String],
114   hashedPassword: Option[String],
115   enabled: Boolean,
116   emailVerified: Boolean,
117   userType: UserType,
118   lastConnection: Option[ZonedDateTime],
119   verificationToken: Option[String],
120   verificationTokenExpiresAt: Option[ZonedDateTime],
121   resetToken: Option[String],
122   resetTokenExpiresAt: Option[ZonedDateTime],
123   roles: Seq[Role],
124   country: Country,
125   language: Language,
126   profile: Option[Profile],
127   override val createdAt: Option[ZonedDateTime] = None,
128   override val updatedAt: Option[ZonedDateTime] = None,
129   isHardBounce: Boolean = false,
130   lastMailingError: Option[MailingErrorLog] = None,
131   organisationName: Option[String] = None,
132   publicProfile: Boolean = false,
133   availableQuestions: Seq[QuestionId],
134   availableEvents: Seq[EventId],
135   privacyPolicyApprovalDate: Option[ZonedDateTime] = None,
136   connectionAttemptsSinceLastSuccessful: Int = 0,
137   lastFailedConnectionAttempt: Option[ZonedDateTime] = None
138 ) extends MakeSerializable
139     with Timestamped {
140 
141   def fullName: Option[String] = {
142     User.fullName(firstName, lastName, organisationName)
143   }
144 
145   def displayName: Option[String] = this.userType match {
146     case UserTypeAnonymous    => None
147     case UserTypeUser         => this.firstName
148     case UserTypeOrganisation => this.organisationName
149     case UserTypePersonality  => this.fullName
150     case UserTypeVirtual      => this.fullName
151     case UserTypeExternal     => this.fullName
152   }
153 
154   def verificationTokenIsExpired: Boolean =
155     verificationTokenExpiresAt.forall(_.isBefore(DateHelper.now()))
156 
157   def resetTokenIsExpired: Boolean =
158     resetTokenExpiresAt.forall(_.isBefore(DateHelper.now()))
159 
160   def hasRole(role: Role): Boolean = {
161     roles.contains(role)
162   }
163 
164 }
165 
166 object User {
167   def fullName(
168     firstName: Option[String],
169     lastName: Option[String],
170     organisationName: Option[String]
171   ): Option[String] = {
172     (firstName, lastName, organisationName) match {
173       case (None, None, None)                                 => None
174       case (None, None, Some(definedOrganisationName))        => Some(definedOrganisationName)
175       case (Some(definedFirstName), None, _)                  => Some(definedFirstName)
176       case (None, Some(definedLastName), _)                   => Some(definedLastName)
177       case (Some(definedFirstName), Some(definedLastName), _) => Some(s"$definedFirstName $definedLastName")
178     }
179   }
180 }
181 
182 final case class UserId(value: String) extends StringValue
183 
184 object UserId {
185   implicit lazy val userIdEncoder: Encoder[UserId] = (a: UserId) => Json.fromString(a.value)
186   implicit lazy val userIdDecoder: Decoder[UserId] =
187     Decoder.decodeString.map(UserId(_))
188 
189   implicit val userIdCodec: JsonValueCodec[UserId] =
190     StringValue.makeCodec(UserId.apply)
191 
192   implicit val userIdFormatter: JsonFormat[UserId] = SprayJsonFormatters.forStringValue(UserId.apply)
193 
194 }
195 
196 sealed abstract class ConnectionMode(val value: String) extends StringEnumEntry with Product with Serializable
197 
198 object ConnectionMode extends StringEnum[ConnectionMode] with StringCirceEnum[ConnectionMode] {
199 
200   case object Mail extends ConnectionMode("MAIL")
201   case object Facebook extends ConnectionMode("FACEBOOK")
202   case object Google extends ConnectionMode("GOOGLE")
203 
204   override val values: IndexedSeq[ConnectionMode] = findValues
205   final val swaggerAllowableValues = "MAIL,FACEBOOK,GOOGLE"
206 }
207 
208 final case class ReconnectInfo(
209   reconnectToken: String,
210   firstName: Option[String],
211   @(ApiModelProperty @field)(dataType = "string", example = "https://example.com/avatar.png")
212   avatarUrl: Option[String],
213   @(ApiModelProperty @field)(dataType = "string", example = "y**********t@make.org", required = true) hiddenMail: String,
214   @(ApiModelProperty @field)(
215     dataType = "list[string]",
216     allowableValues = ConnectionMode.swaggerAllowableValues,
217     required = true
218   )
219   connectionMode: Seq[ConnectionMode]
220 )
221 
222 object ReconnectInfo {
223   implicit lazy val encoder: Encoder[ReconnectInfo] = deriveEncoder[ReconnectInfo]
224   implicit lazy val decoder: Decoder[ReconnectInfo] = deriveDecoder[ReconnectInfo]
225 }
226 
227 // iat = issued at
228 // exp = expiration time
229 final case class OidcInfo(externalUserId: Option[String], iat: Long, exp: Long)
230 
231 object OidcInfo {
232 
233   implicit val oidcInfosBinder: Binders[Option[OidcInfo]] =
234     Binders.string.xmap(l => decode[OidcInfo](l).toOption, _.asJson.noSpaces)
235 
236   implicit lazy val encoder: Encoder[OidcInfo] = deriveEncoder[OidcInfo]
237   implicit lazy val decoder: Decoder[OidcInfo] = deriveDecoder[OidcInfo]
238 }
Line Stmt Id Pos Tree Symbol Tests Code
53 3029 1857 - 1874 Apply org.make.core.user.CustomRole.apply akka.http.scaladsl.testkit.routetest,org.make.core.user.usertest CustomRole.apply(value)
70 912 2725 - 2939 Literal <nosymbol> "ROLE_SUPER_ADMIN,ROLE_ADMIN,ROLE_OPERATOR,ROLE_MODERATOR,ROLE_DIALOGUE_MODERATOR,ROLE_POLITICAL,ROLE_CITIZEN,ROLE_ACTOR,ROLE_PANORAMIC_ADMIN,ROLE_PANORAMIC_EDITOR,ROLE_PANORAMIC_SERVICE,ROLE_PANORAMIC_DATA,custom"
79 5016 3294 - 3306 Select org.make.core.user.UserType.UserTypeUser org.make.api.user.persistentuserservicecomponenttest UserType.this.UserTypeUser
89 3300 3769 - 3827 Literal <nosymbol> "ANONYMOUS,USER,ORGANISATION,PERSONALITY,VIRTUAL,EXTERNAL"
93 4668 3981 - 4010 Select org.make.core.user.UserType.UserTypeOrganisation org.make.api.technical.crm.crmservicecomponenttest UserType.UserTypeOrganisation
93 1401 4021 - 4034 Apply org.make.core.user.HasUserType.userType org.make.api.technical.crm.crmservicecomponenttest h.userType(UserTypeOps.this.t)
93 2606 4032 - 4033 Select org.make.core.user.UserType.UserTypeOps.t org.make.api.technical.crm.crmservicecomponenttest UserTypeOps.this.t
93 1332 3951 - 3979 Select org.make.core.user.UserType.UserTypePersonality org.make.api.technical.crm.crmservicecomponenttest UserType.UserTypePersonality
93 4947 3947 - 4035 Apply scala.collection.SetOps.contains org.make.api.technical.crm.crmservicecomponenttest scala.Predef.Set.apply[org.make.core.user.UserType](UserType.UserTypePersonality, UserType.UserTypeOrganisation).contains(h.userType(UserTypeOps.this.t))
94 2978 4100 - 4101 Select org.make.core.user.UserType.UserTypeOps.t UserTypeOps.this.t
94 917 4106 - 4127 Select org.make.core.user.UserType.UserTypeUser UserType.UserTypeUser
94 4254 4089 - 4127 Apply java.lang.Object.== h.userType(UserTypeOps.this.t).==(UserType.UserTypeUser)
103 3224 4262 - 4272 Select org.make.core.user.User.userType x$1.userType
142 1339 5505 - 5514 Select org.make.core.user.User.firstName org.make.api.proposal.proposalservicetest,org.make.core.user.usertest,org.make.api.proposal.moderationproposalapitest User.this.firstName
142 4490 5516 - 5524 Select org.make.core.user.User.lastName org.make.api.proposal.proposalservicetest,org.make.core.user.usertest,org.make.api.proposal.moderationproposalapitest User.this.lastName
142 2549 5526 - 5542 Select org.make.core.user.User.organisationName org.make.api.proposal.proposalservicetest,org.make.core.user.usertest,org.make.api.proposal.moderationproposalapitest User.this.organisationName
142 1451 5491 - 5543 Apply org.make.core.user.User.fullName org.make.api.proposal.proposalservicetest,org.make.core.user.usertest,org.make.api.proposal.moderationproposalapitest User.fullName(User.this.firstName, User.this.lastName, User.this.organisationName)
145 4878 5585 - 5598 Select org.make.core.user.User.userType org.make.core.user.usertest this.userType
146 2987 5640 - 5644 Select scala.None scala.None
147 923 5678 - 5692 Select org.make.core.user.User.firstName org.make.core.user.usertest this.firstName
148 4189 5726 - 5747 Select org.make.core.user.User.organisationName org.make.core.user.usertest this.organisationName
149 3097 5781 - 5794 Select org.make.core.user.User.fullName org.make.core.user.usertest this.fullName
150 1279 5828 - 5841 Select org.make.core.user.User.fullName this.fullName
151 4497 5875 - 5888 Select org.make.core.user.User.fullName this.fullName
155 4733 5942 - 6005 Apply scala.Option.forall User.this.verificationTokenExpiresAt.forall(((x$2: java.time.ZonedDateTime) => x$2.isBefore(org.make.core.DateHelper.now())))
155 435 5976 - 6004 Apply java.time.chrono.ChronoZonedDateTime.isBefore x$2.isBefore(org.make.core.DateHelper.now())
155 2557 5987 - 6003 Apply org.make.core.DefaultDateHelper.now org.make.core.DateHelper.now()
158 2855 6086 - 6102 Apply org.make.core.DefaultDateHelper.now org.make.core.DateHelper.now()
158 872 6075 - 6103 Apply java.time.chrono.ChronoZonedDateTime.isBefore x$3.isBefore(org.make.core.DateHelper.now())
158 4081 6048 - 6104 Apply scala.Option.forall User.this.resetTokenExpiresAt.forall(((x$3: java.time.ZonedDateTime) => x$3.isBefore(org.make.core.DateHelper.now())))
161 3039 6149 - 6169 Apply scala.collection.SeqOps.contains User.this.roles.contains[org.make.core.user.Role](role)
173 1113 6447 - 6451 Select scala.None org.make.core.user.usertest scala.None
174 4503 6517 - 6546 Apply scala.Some.apply org.make.core.user.usertest scala.Some.apply[String](definedOrganisationName)
175 2503 6612 - 6634 Apply scala.Some.apply org.make.core.user.usertest scala.Some.apply[String](definedFirstName)
176 440 6700 - 6721 Apply scala.Some.apply org.make.core.user.usertest scala.Some.apply[String](definedLastName)
177 4676 6787 - 6830 Apply scala.Some.apply org.make.core.user.usertest,org.make.api.proposal.moderationproposalapitest,org.make.api.technical.crm.crmservicecomponenttest scala.Some.apply[String](("".+(definedFirstName).+(" ").+(definedLastName): String))
190 880 7164 - 7199 Apply org.make.core.StringValue.makeCodec org.make.api.avro.avrocompatibilitytest,akka.http.scaladsl.testkit.routetest,org.make.api.technical.elasticsearch.proposalindexationstreamtest,org.make.api.makekafkatest,org.make.core.proposal.indexed.proposaltest,org.make.api.sessionhistory.sessionhistorycoordinatortest org.make.core.StringValue.makeCodec[org.make.core.user.UserId](((value: String) => UserId.apply(value)))
190 2976 7186 - 7198 Apply org.make.core.user.UserId.apply org.make.core.proposal.indexed.proposaltest UserId.apply(value)
192 4024 7289 - 7301 Apply org.make.core.user.UserId.apply org.make.api.technical.crm.crmservicecomponenttest,org.make.api.userhistory.userhistorytest UserId.apply(value)
192 3052 7254 - 7302 Apply org.make.core.SprayJsonFormatters.forStringValue org.make.api.avro.avrocompatibilitytest,akka.http.scaladsl.testkit.routetest,org.make.api.technical.elasticsearch.proposalindexationstreamtest,org.make.api.makekafkatest,org.make.core.proposal.indexed.proposaltest,org.make.api.sessionhistory.sessionhistorycoordinatortest org.make.core.SprayJsonFormatters.forStringValue[org.make.core.user.UserId](((value: String) => UserId.apply(value)))
205 1337 7779 - 7801 Literal <nosymbol> "MAIL,FACEBOOK,GOOGLE"
234 391 8779 - 8796 Select io.circe.Json.noSpaces io.circe.syntax.`package`.EncoderOps[Option[org.make.core.user.OidcInfo]](x$5).asJson(circe.this.Encoder.encodeOption[org.make.core.user.OidcInfo](OidcInfo.this.encoder)).noSpaces
234 4608 8749 - 8777 Select scala.util.Either.toOption io.circe.parser.`package`.decode[org.make.core.user.OidcInfo](l)(OidcInfo.this.decoder).toOption
234 2513 8781 - 8781 ApplyToImplicitArgs io.circe.Encoder.encodeOption circe.this.Encoder.encodeOption[org.make.core.user.OidcInfo](OidcInfo.this.encoder)
234 4690 8724 - 8797 Apply scalikejdbc.Binders.xmap org.scalatest.testsuite,org.make.api.user.persistentuserservicecomponenttest scalikejdbc.Binders.string.xmap[Option[org.make.core.user.OidcInfo]](((l: String) => io.circe.parser.`package`.decode[org.make.core.user.OidcInfo](l)(OidcInfo.this.decoder).toOption), ((x$5: Option[org.make.core.user.OidcInfo]) => io.circe.syntax.`package`.EncoderOps[Option[org.make.core.user.OidcInfo]](x$5).asJson(circe.this.Encoder.encodeOption[org.make.core.user.OidcInfo](OidcInfo.this.encoder)).noSpaces))