From c7fbabe6529c8cfe6ea364d61cc07c9345313808 Mon Sep 17 00:00:00 2001 From: To-om Date: Thu, 13 Jan 2022 18:21:48 +0100 Subject: [PATCH] #2309 Add API to reset failed attempts counter --- .../thp/thehive/controllers/v1/Router.scala | 1 + .../thp/thehive/controllers/v1/UserCtrl.scala | 26 +++++++++++++++++-- .../services/LocalPasswordAuthSrv.scala | 11 +++++++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/thehive/app/org/thp/thehive/controllers/v1/Router.scala b/thehive/app/org/thp/thehive/controllers/v1/Router.scala index b4edaa7f00..c9e1054fef 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Router.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Router.scala @@ -98,6 +98,7 @@ class Router @Inject() ( case DELETE(p"/user/$userId/key") => userCtrl.removeKey(userId) case POST(p"/user/$userId/key/renew") => userCtrl.renewKey(userId) case GET(p"/user/$userId/avatar$file*") => userCtrl.avatar(userId) + case POST(p"/user/$userId/reset") => userCtrl.resetFailedAttempts(userId) case POST(p"/organisation") => organisationCtrl.create case GET(p"/organisation/$organisationId") => organisationCtrl.get(organisationId) diff --git a/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala index deb05d1af2..c36e9ce395 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/UserCtrl.scala @@ -1,12 +1,12 @@ package org.thp.thehive.controllers.v1 -import org.thp.scalligraph.auth.AuthSrv +import org.thp.scalligraph.auth.{AuthSrv, MultiAuthSrv} import org.thp.scalligraph.controllers.{Entrypoint, FieldsParser} import org.thp.scalligraph.models.Database import org.thp.scalligraph.query.{ParamQuery, PublicProperties, Query} import org.thp.scalligraph.traversal.TraversalOps._ import org.thp.scalligraph.traversal.{IteratorOutput, Traversal} -import org.thp.scalligraph.{AuthorizationError, BadRequestError, EntityIdOrName, NotFoundError, RichOptionTry} +import org.thp.scalligraph.{AuthorizationError, BadRequestError, EntityIdOrName, NotFoundError, NotSupportedError, RichOptionTry} import org.thp.thehive.controllers.v1.Conversion._ import org.thp.thehive.dto.v1.InputUser import org.thp.thehive.models._ @@ -123,6 +123,28 @@ class UserCtrl @Inject() ( } yield Results.NoContent } + lazy val localPasswordAuthSrv: Try[LocalPasswordAuthSrv] = { + def getLocalPasswordAuthSrv(authSrv: AuthSrv): Option[LocalPasswordAuthSrv] = + authSrv match { + case lpas: LocalPasswordAuthSrv => Some(lpas) + case mas: MultiAuthSrv => mas.authProviders.flatMap(getLocalPasswordAuthSrv).headOption + } + getLocalPasswordAuthSrv(authSrv) match { + case Some(lpas) => Success(lpas) + case None => Failure(NotSupportedError("The local password authentication is not enabled")) + } + } + + def resetFailedAttempts(userIdOrName: String): Action[AnyContent] = + entrypoint("reset user") + .authTransaction(db) { implicit request => implicit graph => + for { + lpas <- localPasswordAuthSrv + user <- userSrv.current.organisations(Permissions.manageUser).users.get(EntityIdOrName(userIdOrName)).getOrFail("User") + _ <- lpas.resetFailedAttempts(user) + } yield Results.NoContent + } + def delete(userIdOrName: String, organisation: Option[String]): Action[AnyContent] = entrypoint("delete user") .authTransaction(db) { implicit request => implicit graph => diff --git a/thehive/app/org/thp/thehive/services/LocalPasswordAuthSrv.scala b/thehive/app/org/thp/thehive/services/LocalPasswordAuthSrv.scala index 72104d5bd4..6881b6f95c 100644 --- a/thehive/app/org/thp/thehive/services/LocalPasswordAuthSrv.scala +++ b/thehive/app/org/thp/thehive/services/LocalPasswordAuthSrv.scala @@ -3,6 +3,7 @@ package org.thp.thehive.services import io.github.nremond.SecureHash import org.thp.scalligraph.auth.{AuthCapability, AuthContext, AuthSrv, AuthSrvProvider} import org.thp.scalligraph.models.{Database, Entity} +import org.thp.scalligraph.traversal.Graph import org.thp.scalligraph.traversal.TraversalOps._ import org.thp.scalligraph.utils.Hasher import org.thp.scalligraph.{AuthenticationError, AuthorizationError, EntityIdOrName} @@ -67,7 +68,15 @@ class LocalPasswordAuthSrv(db: Database, userSrv: UserSrv, localUserSrv: LocalUs .getOrFail("User") } isValid - } else false + } else { + logger.warn( + s"Authentication of ${user.login} is refused because the max attempts is reached (${user.failedAttempts.orNull}/${maxAttempts.orNull})" + ) + false + } + + def resetFailedAttempts(user: User with Entity)(implicit graph: Graph): Try[Unit] = + userSrv.get(user).update(_.failedAttempts, None).update(_.lastFailed, None).getOrFail("User").map(_ => ()) override def authenticate(username: String, password: String, organisation: Option[EntityIdOrName], code: Option[String])(implicit request: RequestHeader