Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permet la suppression des comptes jamais connectés #1848

Merged
merged 1 commit into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/actions/LoginAction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ class BaseLoginAction(
)
}
if (token.isActive) {
userService.recordLogin(user.id)
val url = request.path + queryToString(
request.queryString - Keys.QueryParam.key - Keys.QueryParam.token
)
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/CSVImportController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -338,10 +338,11 @@ case class CSVImportController @Inject() (
groupIds = groupId :: Nil,
cguAcceptationDate = None,
newsletterAcceptationDate = None,
firstLoginDate = none,
phoneNumber = userData.user.phoneNumber,
observableOrganisationIds = Nil,
sharedAccount = userData.user.name.nonEmpty,
internalSupportComment = None
internalSupportComment = None,
)
)
}
Expand Down
1 change: 1 addition & 0 deletions app/controllers/SignupController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ case class SignupController @Inject() (
groupIds = form.groupId :: Nil,
cguAcceptationDate = Time.nowParis().some,
newsletterAcceptationDate = None,
firstLoginDate = Instant.now().some,
phoneNumber = form.phoneNumber,
observableOrganisationIds = Nil,
sharedAccount = form.sharedAccount,
Expand Down
60 changes: 29 additions & 31 deletions app/controllers/UserController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -361,15 +361,15 @@ case class UserController @Inject() (
) { () =>
val form = EditUserFormData.form.fill(EditUserFormData.fromUser(otherUser))
val groups = groupService.allOrThrow
val unused = not(isAccountUsed(otherUser))
val Token(tokenName, tokenValue) = CSRF.getToken.get
eventService
.log(
UserShowed,
"Visualise la vue de modification l'utilisateur",
involvesUser = Some(otherUser.id)
)
Future(
userService.isAccountUsed(userId).map { isAccountUsed =>
val unused = not(isAccountUsed)
val Token(tokenName, tokenValue) = CSRF.getToken.get
eventService
.log(
UserShowed,
"Visualise la vue de modification l'utilisateur",
involvesUser = Some(otherUser.id)
)
Ok(
views.html.editUser(request.currentUser, request.rights)(
form,
Expand All @@ -380,41 +380,38 @@ case class UserController @Inject() (
tokenValue = tokenValue
)
)
)
}
}
}
}
}

def isAccountUsed(user: User): Boolean =
applicationService.allForUserId(userId = user.id, anonymous = false).nonEmpty

def deleteUnusedUserById(userId: UUID): Action[AnyContent] =
loginAction.async { implicit request =>
withUser(userId, includeDisabled = true) { user: User =>
asAdminOfUserZone(user)(
DeleteUserUnauthorized,
s"Suppression de l'utilisateur $userId refusée"
) { () =>
if (isAccountUsed(user)) {
eventService.log(
UserIsUsed,
s"Le compte ${user.id} est utilisé",
involvesUser = user.id.some
)
Future(Unauthorized("User is not unused."))
} else {
userService.deleteById(userId)
val flashMessage = s"Utilisateur $userId / ${user.email} supprimé"
eventService.log(
UserDeleted,
s"Utilisateur ${user.id} supprimé",
s"Utilisateur ${user.toLogString}".some,
involvesUser = Some(user.id)
)
Future(
userService.isAccountUsed(userId).map { isAccountUsed =>
if (isAccountUsed) {
eventService.log(
UserIsUsed,
s"Le compte ${user.id} est utilisé",
involvesUser = user.id.some
)
Unauthorized("User is not unused.")
} else {
userService.deleteById(userId)
val flashMessage = s"Utilisateur $userId / ${user.email} supprimé"
eventService.log(
UserDeleted,
s"Utilisateur ${user.id} supprimé",
s"Utilisateur ${user.toLogString}".some,
involvesUser = Some(user.id)
)
Redirect(routes.UserController.home).flashing("success" -> flashMessage)
)
}
}
}
}
Expand Down Expand Up @@ -606,6 +603,7 @@ case class UserController @Inject() (
groupIds = group.id :: Nil,
cguAcceptationDate = None,
newsletterAcceptationDate = None,
firstLoginDate = none,
phoneNumber = userToAdd.phoneNumber,
observableOrganisationIds = Nil,
sharedAccount = userToAdd.sharedAccount,
Expand Down
5 changes: 5 additions & 0 deletions app/helper/Time.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@ object Time {
override def compare(x: ZonedDateTime, y: ZonedDateTime): Int = x.compareTo(y)
}

implicit final val instantInstance: Order[Instant] =
new Order[Instant] {
override def compare(x: Instant, y: Instant): Int = x.compareTo(y)
}

}
19 changes: 16 additions & 3 deletions app/models/User.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package models

import java.time.{ZoneId, ZonedDateTime}
import java.time.{Instant, ZoneId, ZonedDateTime}
import java.util.UUID

import cats.syntax.all._
import constants.Constants
import helper.{Hash, Pseudonymizer, Time, UUIDHelper}
import helper.Time.zonedDateTimeInstance
import helper.Time.{instantInstance, zonedDateTimeInstance}
import helper.StringHelper.{
capitalizeName,
commonStringInputNormalization,
Expand Down Expand Up @@ -40,6 +40,7 @@ case class User(
groupIds: List[UUID] = Nil,
cguAcceptationDate: Option[ZonedDateTime] = None,
newsletterAcceptationDate: Option[ZonedDateTime] = None,
firstLoginDate: Option[Instant],
phoneNumber: Option[String] = None,
// If this field is non empty, then the User
// is considered to be an observer:
Expand All @@ -49,7 +50,7 @@ case class User(
observableOrganisationIds: List[Organisation.Id] = Nil,
sharedAccount: Boolean = false,
// This is a comment only visible by the admins
internalSupportComment: Option[String]
internalSupportComment: Option[String],
) extends AgeModel {
def nameWithQualite = s"$name ( $qualite )"

Expand Down Expand Up @@ -105,6 +106,9 @@ case class User(
lazy val newsletterAcceptationDateLog: String =
newsletterAcceptationDate.map(Time.adminsFormatter.format).getOrElse("<vide>")

lazy val firstLoginDateLog: String =
firstLoginDate.map(Time.adminsFormatter.format).getOrElse("<vide>")

lazy val phoneNumberLog: String = phoneNumber.map(withQuotes).getOrElse("<vide>")
lazy val observableOrganisationIdsLog: String = observableOrganisationIds.map(_.id).mkString(", ")
lazy val sharedAccountLog: String = sharedAccount.toString
Expand Down Expand Up @@ -132,6 +136,7 @@ case class User(
("Territoires", areasLog),
("Date CGU", cguAcceptationDateLog),
("Newsletter", newsletterAcceptationDateLog),
("Première connexion", firstLoginDateLog),
("Observation des organismes", observableOrganisationIdsLog),
("Information Support", internalSupportCommentLog),
).map { case (fieldName, value) => s"$fieldName : $value" }.mkString(" | ") + "]"
Expand Down Expand Up @@ -171,6 +176,12 @@ case class User(
newsletterAcceptationDateLog,
other.newsletterAcceptationDateLog
),
(
"Première connexion",
firstLoginDate =!= other.firstLoginDate,
firstLoginDateLog,
other.firstLoginDateLog
),
(
"Observation des organismes",
observableOrganisationIds =!= other.observableOrganisationIds,
Expand Down Expand Up @@ -211,6 +222,7 @@ case class User(
groupIds = groupIds,
cguAcceptationDate = cguAcceptationDate,
newsletterAcceptationDate = newsletterAcceptationDate,
firstLoginDate = firstLoginDate,
phoneNumber = pseudoPhone,
observableOrganisationIds = observableOrganisationIds,
sharedAccount = sharedAccount,
Expand Down Expand Up @@ -238,6 +250,7 @@ object User {
"75056",
groupAdmin = false,
disabled = true,
firstLoginDate = none,
internalSupportComment = None
)

Expand Down
3 changes: 3 additions & 0 deletions app/models/dataModels.scala
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ object dataModels {
groupIds = user.groupIds.distinct,
cguAcceptationDate = user.cguAcceptationDate.map(_.toInstant),
newsletterAcceptationDate = user.newsletterAcceptationDate.map(_.toInstant),
firstLoginDate = user.firstLoginDate,
phoneNumber = user.phoneNumber,
observableOrganisationIds = user.observableOrganisationIds.distinct.map(_.id),
sharedAccount = user.sharedAccount,
Expand Down Expand Up @@ -319,6 +320,7 @@ object dataModels {
groupIds: List[UUID],
cguAcceptationDate: Option[Instant],
newsletterAcceptationDate: Option[Instant],
firstLoginDate: Option[Instant],
phoneNumber: Option[String],
observableOrganisationIds: List[String],
sharedAccount: Boolean,
Expand All @@ -345,6 +347,7 @@ object dataModels {
groupIds = groupIds,
cguAcceptationDate = cguAcceptationDate.map(_.atZone(Time.timeZoneParis)),
newsletterAcceptationDate = newsletterAcceptationDate.map(_.atZone(Time.timeZoneParis)),
firstLoginDate = firstLoginDate,
phoneNumber = phoneNumber,
observableOrganisationIds = observableOrganisationIds.map(Organisation.Id.apply),
sharedAccount = sharedAccount,
Expand Down
3 changes: 3 additions & 0 deletions app/services/AnonymizedDataService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ class AnonymizedDataService @Inject() (
expert,
cgu_acceptation_date,
newsletter_acceptation_date,
first_login_date,
disabled,
phone_number,
observable_organisation_ids,
Expand All @@ -440,6 +441,7 @@ class AnonymizedDataService @Inject() (
{expert},
{cguAcceptationDate},
{newsletterAcceptationDate},
{firstLoginDate},
{disabled},
{phoneNumber},
array[{observableOrganisationIds}]::varchar[],
Expand All @@ -464,6 +466,7 @@ class AnonymizedDataService @Inject() (
"expert" -> row.expert,
"cguAcceptationDate" -> row.cguAcceptationDate,
"newsletterAcceptationDate" -> row.newsletterAcceptationDate,
"firstLoginDate" -> row.firstLoginDate,
"disabled" -> row.disabled,
"phoneNumber" -> row.phoneNumber,
"observableOrganisationIds" -> row.observableOrganisationIds,
Expand Down
43 changes: 42 additions & 1 deletion app/services/UserService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import cats.syntax.all._
import helper.StringHelper.StringOps
import helper.{Hash, StringHelper, Time, UUIDHelper}
import java.sql.Connection
import java.time.Instant
import java.util.UUID
import javax.inject.Inject
import models.{Error, EventType, Organisation, User}
Expand Down Expand Up @@ -44,6 +45,7 @@ class UserService @Inject() (
"group_ids",
"cgu_acceptation_date",
"newsletter_acceptation_date",
"first_login_date",
"phone_number",
"observable_organisation_ids",
"shared_account",
Expand Down Expand Up @@ -211,6 +213,32 @@ class UserService @Inject() (
)
)

def isAccountUsed(userId: UUID): Future[Boolean] =
Future {
db.withConnection { implicit connection =>
SQL(
"""
SELECT
CASE
WHEN first_login_date IS NOT NULL
AND EXISTS (
SELECT 1
FROM application
WHERE creator_user_id = {userId}::uuid
OR invited_users ?? {userId}
) THEN TRUE
ELSE FALSE
END AS result
FROM
"user"
WHERE
id = {userId}::uuid
"""
).on("userId" -> userId)
.as(SqlParser.scalar[Boolean].single)
}
}

// Note: empty string will return an `Error`
//
// The configuration is
Expand Down Expand Up @@ -263,7 +291,7 @@ class UserService @Inject() (
assert(row.areas.nonEmpty)
success && SQL"""
INSERT INTO "user" (id, key, first_name, last_name, name, qualite, email, helper, instructor, admin, areas, creation_date,
commune_code, group_admin, group_ids, cgu_acceptation_date, expert, phone_number, shared_account) VALUES (
commune_code, group_admin, group_ids, cgu_acceptation_date, first_login_date, expert, phone_number, shared_account) VALUES (
${row.id}::uuid,
${Hash.sha256(s"${row.id}${config.appSecret}")},
${row.firstName},
Expand All @@ -280,6 +308,7 @@ class UserService @Inject() (
${row.groupAdmin},
array[${row.groupIds}]::uuid[],
${row.cguAcceptationDate},
${row.firstLoginDate},
${row.expert},
${row.phoneNumber},
${row.sharedAccount})
Expand Down Expand Up @@ -350,6 +379,18 @@ class UserService @Inject() (
""".executeUpdate()
}

def recordLogin(userId: UUID) =
db.withConnection { implicit connection =>
SQL"""
UPDATE "user"
SET
first_login_date = NOW()
WHERE
id = $userId::uuid
AND first_login_date IS NULL
""".executeUpdate()
}

def editProfile(userId: UUID)(
firstName: String,
lastName: String,
Expand Down
4 changes: 2 additions & 2 deletions app/views/editUser.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
@import serializers.Keys
@import cats.implicits.catsSyntaxEq

@(currentUser: User, currentUserRights: Authorization.UserRights)(form: Form[EditUserFormData], uneditedUser: User, userGroups: List[UserGroup], unused: Boolean = false, tokenName: String = "", tokenValue: String = "")(implicit webJarsUtil: org.webjars.play.WebJarsUtil, flash: Flash, messagesProvider: MessagesProvider, request: RequestHeader, mainInfos: MainInfos)
@(currentUser: User, currentUserRights: Authorization.UserRights)(form: Form[EditUserFormData], uneditedUser: User, userGroups: List[UserGroup], canDeleteUser: Boolean = false, tokenName: String = "", tokenValue: String = "")(implicit webJarsUtil: org.webjars.play.WebJarsUtil, flash: Flash, messagesProvider: MessagesProvider, request: RequestHeader, mainInfos: MainInfos)


@main(currentUser, currentUserRights, modals = toHtml(views.user.deleteUserModal(uneditedUser)))(s"Utilisateur ${form("name").value.getOrElse("")}") {
Expand Down Expand Up @@ -200,7 +200,7 @@
value="@uneditedUser.internalSupportComment">
}

@if(unused && canEditUser) {
@if(canDeleteUser && canEditUser) {
<button id="delete-user-button" class="mdl-button mdl-js-button mdl-button--raised" type="button">Supprimer cet utilisateur inutilisé</button>
<br>
}
Expand Down
9 changes: 9 additions & 0 deletions conf/evolutions/default/68.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- !Ups

ALTER TABLE "user" ADD COLUMN first_login_date timestamp with time zone DEFAULT NOW();



-- !Downs

ALTER TABLE "user" DROP COLUMN first_login_date;
1 change: 1 addition & 0 deletions test/browser/AnswerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ class AnswerSpec extends Specification with Tables with BaseSpec {
disabled = false,
expert = isExpert,
cguAcceptationDate = Some(Time.nowParis()),
firstLoginDate = None,
groupIds = groups.map(_.id),
internalSupportComment = None
)
Expand Down
Loading