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 RoleConsultationModerator extends Role("ROLE_CONSULTATION_MODERATOR")
61   case object RolePolitical extends Role("ROLE_POLITICAL")
62   case object RoleCitizen extends Role("ROLE_CITIZEN")
63   case object RoleActor extends Role("ROLE_ACTOR")
64   case object RolePanoramicAdmin extends Role("ROLE_PANORAMIC_ADMIN")
65   case object RolePanoramicEditor extends Role("ROLE_PANORAMIC_EDITOR")
66   case object RolePanoramicService extends Role("ROLE_PANORAMIC_SERVICE")
67   case object RolePanoramicData extends Role("ROLE_PANORAMIC_DATA")
68 
69   override val values: IndexedSeq[Role] = findValues
70   final val swaggerAllowableValues =
71     "ROLE_SUPER_ADMIN,ROLE_ADMIN,ROLE_OPERATOR,ROLE_MODERATOR,ROLE_DIALOGUE_MODERATOR,ROLE_CONSULTATION_MODERATOR,ROLE_POLITICAL,ROLE_CITIZEN,ROLE_ACTOR,ROLE_PANORAMIC_ADMIN,ROLE_PANORAMIC_EDITOR,ROLE_PANORAMIC_SERVICE,ROLE_PANORAMIC_DATA,custom"
72 }
73 
74 final case class CustomRole(override val value: String) extends Role(value)
75 
76 sealed abstract class UserType(val value: String) extends StringEnumEntry with Product with Serializable
77 
78 object UserType extends StringEnum[UserType] with FallbackingStringCirceEnum[UserType] with JsoniterEnum[UserType] {
79 
80   override def default(value: String): UserType = UserTypeUser
81 
82   case object UserTypeAnonymous extends UserType("ANONYMOUS")
83   case object UserTypeUser extends UserType("USER")
84   case object UserTypeOrganisation extends UserType("ORGANISATION")
85   case object UserTypePersonality extends UserType("PERSONALITY")
86   case object UserTypeVirtual extends UserType("VIRTUAL")
87   case object UserTypeExternal extends UserType("EXTERNAL")
88 
89   override def values: IndexedSeq[UserType] = findValues
90   final val swaggerAllowableValues = "ANONYMOUS,USER,ORGANISATION,PERSONALITY,VIRTUAL,EXTERNAL"
91 
92   implicit class UserTypeOps[T](val t: T) extends AnyVal {
93     def isB2B(implicit h: HasUserType[T]): Boolean =
94       Set(UserType.UserTypePersonality, UserType.UserTypeOrganisation).contains(h.userType(t))
95     def isB2C(implicit h: HasUserType[T]): Boolean = h.userType(t) == UserType.UserTypeUser
96   }
97 }
98 
99 trait HasUserType[T] {
100   def userType(t: T): UserType
101 }
102 
103 object HasUserType {
104   implicit val userUserType: HasUserType[User] = _.userType
105 }
106 
107 final case class MailingErrorLog(error: String, date: ZonedDateTime)
108 
109 final case class User(
110   userId: UserId,
111   email: String,
112   firstName: Option[String],
113   lastName: Option[String],
114   lastIp: Option[String],
115   hashedPassword: Option[String],
116   enabled: Boolean,
117   emailVerified: Boolean,
118   userType: UserType,
119   lastConnection: Option[ZonedDateTime],
120   verificationToken: Option[String],
121   verificationTokenExpiresAt: Option[ZonedDateTime],
122   resetToken: Option[String],
123   resetTokenExpiresAt: Option[ZonedDateTime],
124   roles: Seq[Role],
125   country: Country,
126   language: Language,
127   profile: Option[Profile],
128   override val createdAt: Option[ZonedDateTime] = None,
129   override val updatedAt: Option[ZonedDateTime] = None,
130   isHardBounce: Boolean = false,
131   lastMailingError: Option[MailingErrorLog] = None,
132   organisationName: Option[String] = None,
133   publicProfile: Boolean = false,
134   availableQuestions: Seq[QuestionId],
135   availableEvents: Seq[EventId],
136   privacyPolicyApprovalDate: Option[ZonedDateTime] = None,
137   connectionAttemptsSinceLastSuccessful: Int = 0,
138   lastFailedConnectionAttempt: Option[ZonedDateTime] = None
139 ) extends MakeSerializable
140     with Timestamped {
141 
142   def fullName: Option[String] = {
143     User.fullName(firstName, lastName, organisationName)
144   }
145 
146   def displayName: Option[String] = this.userType match {
147     case UserTypeAnonymous    => None
148     case UserTypeUser         => this.firstName
149     case UserTypeOrganisation => this.organisationName
150     case UserTypePersonality  => this.fullName
151     case UserTypeVirtual      => this.fullName
152     case UserTypeExternal     => this.fullName
153   }
154 
155   def verificationTokenIsExpired: Boolean =
156     verificationTokenExpiresAt.forall(_.isBefore(DateHelper.now()))
157 
158   def resetTokenIsExpired: Boolean =
159     resetTokenExpiresAt.forall(_.isBefore(DateHelper.now()))
160 
161   def hasRole(role: Role): Boolean = {
162     roles.contains(role)
163   }
164 
165 }
166 
167 object User {
168   def fullName(
169     firstName: Option[String],
170     lastName: Option[String],
171     organisationName: Option[String]
172   ): Option[String] = {
173     (firstName, lastName, organisationName) match {
174       case (None, None, None)                                 => None
175       case (None, None, Some(definedOrganisationName))        => Some(definedOrganisationName)
176       case (Some(definedFirstName), None, _)                  => Some(definedFirstName)
177       case (None, Some(definedLastName), _)                   => Some(definedLastName)
178       case (Some(definedFirstName), Some(definedLastName), _) => Some(s"$definedFirstName $definedLastName")
179     }
180   }
181 }
182 
183 final case class UserId(value: String) extends StringValue
184 
185 object UserId {
186   implicit lazy val userIdEncoder: Encoder[UserId] = (a: UserId) => Json.fromString(a.value)
187   implicit lazy val userIdDecoder: Decoder[UserId] =
188     Decoder.decodeString.map(UserId(_))
189 
190   implicit val userIdCodec: JsonValueCodec[UserId] =
191     StringValue.makeCodec(UserId.apply)
192 
193   implicit val userIdFormatter: JsonFormat[UserId] = SprayJsonFormatters.forStringValue(UserId.apply)
194 
195 }
196 
197 sealed abstract class ConnectionMode(val value: String) extends StringEnumEntry with Product with Serializable
198 
199 object ConnectionMode extends StringEnum[ConnectionMode] with StringCirceEnum[ConnectionMode] {
200 
201   case object Mail extends ConnectionMode("MAIL")
202   case object Facebook extends ConnectionMode("FACEBOOK")
203   case object Google extends ConnectionMode("GOOGLE")
204 
205   override val values: IndexedSeq[ConnectionMode] = findValues
206   final val swaggerAllowableValues = "MAIL,FACEBOOK,GOOGLE"
207 }
208 
209 final case class ReconnectInfo(
210   reconnectToken: String,
211   firstName: Option[String],
212   @(ApiModelProperty @field)(dataType = "string", example = "https://example.com/avatar.png")
213   avatarUrl: Option[String],
214   @(ApiModelProperty @field)(dataType = "string", example = "y**********t@make.org", required = true) hiddenMail: String,
215   @(ApiModelProperty @field)(
216     dataType = "list[string]",
217     allowableValues = ConnectionMode.swaggerAllowableValues,
218     required = true
219   )
220   connectionMode: Seq[ConnectionMode]
221 )
222 
223 object ReconnectInfo {
224   implicit lazy val encoder: Encoder[ReconnectInfo] = deriveEncoder[ReconnectInfo]
225   implicit lazy val decoder: Decoder[ReconnectInfo] = deriveDecoder[ReconnectInfo]
226 }
227 
228 // iat = issued at
229 // exp = expiration time
230 final case class OidcInfo(externalUserId: Option[String], iat: Long, exp: Long)
231 
232 object OidcInfo {
233 
234   implicit val oidcInfosBinder: Binders[Option[OidcInfo]] =
235     Binders.string.xmap(l => decode[OidcInfo](l).toOption, _.asJson.noSpaces)
236 
237   implicit lazy val encoder: Encoder[OidcInfo] = deriveEncoder[OidcInfo]
238   implicit lazy val decoder: Decoder[OidcInfo] = deriveDecoder[OidcInfo]
239 }
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)
71 912 2809 - 3051 Literal <nosymbol> "ROLE_SUPER_ADMIN,ROLE_ADMIN,ROLE_OPERATOR,ROLE_MODERATOR,ROLE_DIALOGUE_MODERATOR,ROLE_CONSULTATION_MODERATOR,ROLE_POLITICAL,ROLE_CITIZEN,ROLE_ACTOR,ROLE_PANORAMIC_ADMIN,ROLE_PANORAMIC_EDITOR,ROLE_PANORAMIC_SERVICE,ROLE_PANORAMIC_DATA,custom"
80 5016 3406 - 3418 Select org.make.core.user.UserType.UserTypeUser org.make.api.user.persistentuserservicecomponenttest UserType.this.UserTypeUser
90 3300 3881 - 3939 Literal <nosymbol> "ANONYMOUS,USER,ORGANISATION,PERSONALITY,VIRTUAL,EXTERNAL"
94 4668 4093 - 4122 Select org.make.core.user.UserType.UserTypeOrganisation org.make.api.technical.crm.crmservicecomponenttest UserType.UserTypeOrganisation
94 1401 4133 - 4146 Apply org.make.core.user.HasUserType.userType org.make.api.technical.crm.crmservicecomponenttest h.userType(UserTypeOps.this.t)
94 2606 4144 - 4145 Select org.make.core.user.UserType.UserTypeOps.t org.make.api.technical.crm.crmservicecomponenttest UserTypeOps.this.t
94 1332 4063 - 4091 Select org.make.core.user.UserType.UserTypePersonality org.make.api.technical.crm.crmservicecomponenttest UserType.UserTypePersonality
94 4947 4059 - 4147 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))
95 2978 4212 - 4213 Select org.make.core.user.UserType.UserTypeOps.t UserTypeOps.this.t
95 917 4218 - 4239 Select org.make.core.user.UserType.UserTypeUser UserType.UserTypeUser
95 4254 4201 - 4239 Apply java.lang.Object.== h.userType(UserTypeOps.this.t).==(UserType.UserTypeUser)
104 3224 4374 - 4384 Select org.make.core.user.User.userType x$1.userType
143 1339 5617 - 5626 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
143 4490 5628 - 5636 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
143 2549 5638 - 5654 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
143 1451 5603 - 5655 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)
146 4878 5697 - 5710 Select org.make.core.user.User.userType org.make.core.user.usertest this.userType
147 2987 5752 - 5756 Select scala.None scala.None
148 923 5790 - 5804 Select org.make.core.user.User.firstName org.make.core.user.usertest this.firstName
149 4189 5838 - 5859 Select org.make.core.user.User.organisationName org.make.core.user.usertest this.organisationName
150 3097 5893 - 5906 Select org.make.core.user.User.fullName org.make.core.user.usertest this.fullName
151 1279 5940 - 5953 Select org.make.core.user.User.fullName this.fullName
152 4497 5987 - 6000 Select org.make.core.user.User.fullName this.fullName
156 4733 6054 - 6117 Apply scala.Option.forall User.this.verificationTokenExpiresAt.forall(((x$2: java.time.ZonedDateTime) => x$2.isBefore(org.make.core.DateHelper.now())))
156 435 6088 - 6116 Apply java.time.chrono.ChronoZonedDateTime.isBefore x$2.isBefore(org.make.core.DateHelper.now())
156 2557 6099 - 6115 Apply org.make.core.DefaultDateHelper.now org.make.core.DateHelper.now()
159 2855 6198 - 6214 Apply org.make.core.DefaultDateHelper.now org.make.core.DateHelper.now()
159 872 6187 - 6215 Apply java.time.chrono.ChronoZonedDateTime.isBefore x$3.isBefore(org.make.core.DateHelper.now())
159 4081 6160 - 6216 Apply scala.Option.forall User.this.resetTokenExpiresAt.forall(((x$3: java.time.ZonedDateTime) => x$3.isBefore(org.make.core.DateHelper.now())))
162 3039 6261 - 6281 Apply scala.collection.SeqOps.contains User.this.roles.contains[org.make.core.user.Role](role)
174 1113 6559 - 6563 Select scala.None org.make.core.user.usertest scala.None
175 4503 6629 - 6658 Apply scala.Some.apply org.make.core.user.usertest scala.Some.apply[String](definedOrganisationName)
176 2503 6724 - 6746 Apply scala.Some.apply org.make.core.user.usertest scala.Some.apply[String](definedFirstName)
177 440 6812 - 6833 Apply scala.Some.apply org.make.core.user.usertest scala.Some.apply[String](definedLastName)
178 4676 6899 - 6942 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))
191 880 7276 - 7311 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)))
191 2976 7298 - 7310 Apply org.make.core.user.UserId.apply org.make.core.proposal.indexed.proposaltest UserId.apply(value)
193 4024 7401 - 7413 Apply org.make.core.user.UserId.apply org.make.api.technical.crm.crmservicecomponenttest,org.make.api.userhistory.userhistorytest UserId.apply(value)
193 3052 7366 - 7414 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)))
206 1337 7891 - 7913 Literal <nosymbol> "MAIL,FACEBOOK,GOOGLE"
235 391 8891 - 8908 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
235 4608 8861 - 8889 Select scala.util.Either.toOption io.circe.parser.`package`.decode[org.make.core.user.OidcInfo](l)(OidcInfo.this.decoder).toOption
235 2513 8893 - 8893 ApplyToImplicitArgs io.circe.Encoder.encodeOption circe.this.Encoder.encodeOption[org.make.core.user.OidcInfo](OidcInfo.this.encoder)
235 4690 8836 - 8909 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))