Skip to content

Commit

Permalink
отдельный UserPermissionService
Browse files Browse the repository at this point in the history
  • Loading branch information
maxcom committed Nov 29, 2024
1 parent da987cf commit e88d26b
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 95 deletions.
79 changes: 79 additions & 0 deletions src/main/java/ru/org/linux/user/UserPermissionService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 1998-2024 Linux.org.ru
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ru.org.linux.user

import org.springframework.stereotype.Service
import ru.org.linux.auth.{AuthorizedSession, IPBlockDao}
import ru.org.linux.spring.dao.DeleteInfoDao
import ru.org.linux.user.UserPermissionService.*

import java.time.Duration

object UserPermissionService {
val MaxTotalInvites = 15
val MaxUserInvites = 1
val MaxInviteScoreLoss = 10
val InviteScore = 200
val MaxUnactivatedPerIp = 2
val MaxUserpicScoreLoss = 20
}

@Service
class UserPermissionService(userLogDao: UserLogDao, userInvitesDao: UserInvitesDao, deleteInfoDao: DeleteInfoDao,
ipBlockDao: IPBlockDao, userDao: UserDao) {
def canResetPassword(user: User): Boolean = {
!userLogDao.hasRecentSelfEvent(user, Duration.ofDays(7), UserLogAction.SENT_PASSWORD_RESET)
}

def canInvite(implicit session: AuthorizedSession): Boolean = session.moderator || {
lazy val (totalInvites, userInvites) = userInvitesDao.countValidInvites(session.user)
lazy val userScoreLoss = deleteInfoDao.getRecentScoreLoss(session.user)

!session.user.isFrozen && session.user.getScore > InviteScore &&
totalInvites < MaxTotalInvites &&
userInvites < MaxUserInvites &&
userScoreLoss < MaxInviteScoreLoss
}

def canRegister(remoteAddr: String): Boolean = !ipBlockDao.getBlockInfo(remoteAddr).isBlocked &&
userDao.countUnactivated(remoteAddr) < MaxUnactivatedPerIp

def canLoadUserpic(implicit session: AuthorizedSession): Boolean = {
def userpicSetCount = userLogDao.getUserpicSetCount(session.user, Duration.ofHours(1))

def wasReset = userLogDao.hasRecentModerationEvent(session.user, Duration.ofDays(30), UserLogAction.RESET_USERPIC)

def userScoreLoss = deleteInfoDao.getRecentScoreLoss(session.user)

session.user.getScore >= 45 &&
!session.user.isFrozen &&
(userpicSetCount < 3) &&
!wasReset &&
(userScoreLoss < MaxUserpicScoreLoss)
}

def canEditProfileInfo(implicit session: AuthorizedSession): Boolean = {
import session.user

!user.isFrozen &&
!userLogDao.hasRecentModerationEvent(user, Duration.ofDays(1), UserLogAction.RESET_INFO) &&
!userLogDao.hasRecentModerationEvent(user, Duration.ofDays(1), UserLogAction.RESET_URL) &&
!userLogDao.hasRecentModerationEvent(user, Duration.ofDays(1), UserLogAction.RESET_TOWN)
}

def canResetPasswordByCode(user: User): Boolean =
!user.isBlocked && user.isActivated && !user.isAnonymous && !user.isAdministrator
}

17 changes: 9 additions & 8 deletions src/main/scala/ru/org/linux/user/EditRegisterController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ import javax.validation.Valid
@RequestMapping(Array("/people/{nick}/edit"))
class EditRegisterController(rememberMeServices: RememberMeServices, authenticationManager: AuthenticationManager,
userDetailsService: UserDetailsServiceImpl, ipBlockDao: IPBlockDao, userDao: UserDao,
userService: UserService, emailService: EmailService, resourceLoader: ResourceLoader)
userService: UserService, emailService: EmailService, resourceLoader: ResourceLoader,
userPermissionService: UserPermissionService)
extends StrictLogging {
private val validator = new EditRegisterRequestValidator(resourceLoader)

@RequestMapping(method = Array(RequestMethod.GET))
def show(@ModelAttribute("form") form: EditRegisterRequest, @PathVariable("nick") nick: String,
response: HttpServletResponse): ModelAndView = AuthorizedOnly { currentUser =>
response: HttpServletResponse): ModelAndView = AuthorizedOnly { implicit currentUser =>
if (currentUser.user.getNick != nick) {
throw new AccessViolationException("Not authorized")
}
Expand All @@ -61,8 +62,8 @@ class EditRegisterController(rememberMeServices: RememberMeServices, authenticat

val mv = new ModelAndView("edit-reg")

mv.getModel.put("canLoadUserpic", userService.canLoadUserpic(user))
mv.getModel.put("canEditInfo", userService.canEditProfileInfo(user))
mv.getModel.put("canLoadUserpic", userPermissionService.canLoadUserpic)
mv.getModel.put("canEditInfo", userPermissionService.canEditProfileInfo)

form.setEmail(user.getEmail)
form.setUrl(userInfo.getUrl)
Expand All @@ -78,7 +79,7 @@ class EditRegisterController(rememberMeServices: RememberMeServices, authenticat
@RequestMapping(method = Array(RequestMethod.POST))
def edit(request: HttpServletRequest, response: HttpServletResponse, @PathVariable("nick") nick: String,
@Valid @ModelAttribute("form") form: EditRegisterRequest,
errors: Errors): ModelAndView = AuthorizedOnly { currentUser =>
errors: Errors): ModelAndView = AuthorizedOnly { implicit currentUser =>
if (currentUser.user.getNick != nick) {
throw new AccessViolationException("Not authorized")
}
Expand Down Expand Up @@ -127,7 +128,7 @@ class EditRegisterController(rememberMeServices: RememberMeServices, authenticat
}

if (!errors.hasErrors) {
if (userService.canEditProfileInfo(user)) {
if (userPermissionService.canEditProfileInfo) {
userService.updateUser(user, name, url, newEmail, town, newPassword, info, request.getRemoteAddr)
} else {
userService.updateEmailPasswd(user, newEmail, newPassword, request.getRemoteAddr)
Expand All @@ -152,8 +153,8 @@ class EditRegisterController(rememberMeServices: RememberMeServices, authenticat
} else {
val mv = new ModelAndView("edit-reg")

mv.getModel.put("canLoadUserpic", userService.canLoadUserpic(user))
mv.getModel.put("canEditInfo", userService.canEditProfileInfo(user))
mv.getModel.put("canLoadUserpic", userPermissionService.canLoadUserpic)
mv.getModel.put("canEditInfo", userPermissionService.canEditProfileInfo)

mv
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/ru/org/linux/user/EditSettingsController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ import scala.jdk.CollectionConverters.*

@Controller
@RequestMapping (path = Array ("/people/{nick}/settings") )
class EditSettingsController(userDao: UserDao, profileDao: ProfileDao, userService: UserService) {
class EditSettingsController(userDao: UserDao, profileDao: ProfileDao, userPermissionService: UserPermissionService) {
@RequestMapping(method = Array(RequestMethod.GET))
def showForm(@PathVariable nick: String): ModelAndView = AuthorizedOnly { currentUser =>
def showForm(@PathVariable nick: String): ModelAndView = AuthorizedOnly { implicit currentUser =>
val tmpl = Template.getTemplate
if (!(currentUser.user.getNick == nick)) {
throw new AccessViolationException("Not authorized")
Expand Down Expand Up @@ -63,7 +63,7 @@ class EditSettingsController(userDao: UserDao, profileDao: ProfileDao, userServi

params.put("avatarsList", DefaultProfile.getAvatars)

params.put("canLoadUserpic", Boolean.box(userService.canLoadUserpic(currentUser.user)))
params.put("canLoadUserpic", Boolean.box(userPermissionService.canLoadUserpic))

new ModelAndView("edit-profile", params)
}
Expand Down
7 changes: 4 additions & 3 deletions src/main/scala/ru/org/linux/user/LostPasswordController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import javax.mail.internet.AddressException

@Controller
@RequestMapping(Array("/lostpwd.jsp"))
class LostPasswordController(userDao: UserDao, userService: UserService, emailService: EmailService) {
class LostPasswordController(userDao: UserDao, userService: UserService, emailService: EmailService,
userPermissionService: UserPermissionService) {
@RequestMapping(method = Array(RequestMethod.GET))
def showForm: ModelAndView = new ModelAndView("lostpwd-form")

Expand All @@ -42,15 +43,15 @@ class LostPasswordController(userDao: UserDao, userService: UserService, emailSe
throw new BadInputException("Этот email не зарегистрирован!")
}

if (!userService.canResetPasswordByCode(user)) {
if (!userPermissionService.canResetPasswordByCode(user)) {
throw new AccessViolationException("Пароль этого пользователя нельзя сбросить через email")
}

if (user.isModerator && !currentUser.moderator) {
throw new AccessViolationException("этот пароль могут сбросить только модераторы")
}

if (!currentUser.moderator && !userService.canResetPassword(user)) {
if (!currentUser.moderator && !userPermissionService.canResetPassword(user)) {
throw new BadInputException("Нельзя запрашивать пароль чаще одного раза в неделю!")
}

Expand Down
17 changes: 9 additions & 8 deletions src/main/scala/ru/org/linux/user/RegisterController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class RegisterController(captcha: CaptchaService, rememberMeServices: RememberMe
@Qualifier("authenticationManager") authenticationManager: AuthenticationManager,
userDetailsService: UserDetailsServiceImpl, userDao: UserDao, emailService: EmailService,
siteConfig: SiteConfig, userService: UserService, invitesDao: UserInvitesDao,
resourceLoader: ResourceLoader) extends StrictLogging {
resourceLoader: ResourceLoader,
userPermissionService: UserPermissionService) extends StrictLogging {
private val registerRequestValidator = new RegisterRequestValidator(resourceLoader)

@RequestMapping(value = Array("/register.jsp"), method = Array(RequestMethod.GET))
Expand All @@ -66,7 +67,7 @@ class RegisterController(captcha: CaptchaService, rememberMeServices: RememberMe
}
new ModelAndView("register", "invite", invite)
} else {
if (userService.canRegister(request.getRemoteAddr)) {
if (userPermissionService.canRegister(request.getRemoteAddr)) {
new ModelAndView("register", "permit", makePermit)
} else {
new ModelAndView("no-register")
Expand Down Expand Up @@ -178,7 +179,7 @@ class RegisterController(captcha: CaptchaService, rememberMeServices: RememberMe
@RequestParam activation: String, @RequestParam nick: String,
@RequestParam passwd: String): ModelAndView = {
try {
val details = userDetailsService.loadUserByUsername(nick).asInstanceOf[UserDetailsImpl]
val details = userDetailsService.loadUserByUsername(nick)

if (!details.getUser.isActivated) {
val token = new UsernamePasswordAuthenticationToken(nick, passwd)
Expand All @@ -192,7 +193,7 @@ class RegisterController(captcha: CaptchaService, rememberMeServices: RememberMe
if (regcode.equalsIgnoreCase(activation)) {
userDao.activateUser(userDetails.getUser)

val updatedDetails = userDetailsService.loadUserByUsername(nick).asInstanceOf[UserDetailsImpl]
val updatedDetails = userDetailsService.loadUserByUsername(nick)
token.setDetails(updatedDetails)
val updatedAuth = authenticationManager.authenticate(token)

Expand Down Expand Up @@ -262,17 +263,17 @@ class RegisterController(captcha: CaptchaService, rememberMeServices: RememberMe
}

@RequestMapping(value = Array("create-invite"), method = Array(RequestMethod.GET))
def createInviteForm(): ModelAndView = AuthorizedOnly { currentUser =>
if (!userService.canInvite(currentUser.user)) {
def createInviteForm(): ModelAndView = AuthorizedOnly { implicit currentUser =>
if (!userPermissionService.canInvite) {
throw new AccessViolationException("Вы не можете пригласить нового пользователя")
}

new ModelAndView("create-invite")
}

@RequestMapping(value = Array("create-invite"), method = Array(RequestMethod.POST))
def createInvite(@RequestParam email: String): ModelAndView = AuthorizedOnly { currentUser =>
if (!userService.canInvite(currentUser.user)) {
def createInvite(@RequestParam email: String): ModelAndView = AuthorizedOnly { implicit currentUser =>
if (!userPermissionService.canInvite) {
throw new AccessViolationException("Вы не можете пригласить нового пользователя")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import ru.org.linux.util.StringUtil
import scala.jdk.CollectionConverters.MapHasAsJava

@Controller
class ResetPasswordController(userDao: UserDao, userService: UserService) extends StrictLogging {
class ResetPasswordController(userDao: UserDao, userService: UserService,
userPermissionService: UserPermissionService) extends StrictLogging {
@RequestMapping(value = Array("/people/{nick}/profile"), method = Array(RequestMethod.GET, RequestMethod.HEAD),
params = Array("reset-password"))
def showModeratorForm(@PathVariable nick: String): ModelAndView = ModeratorOnly { _ =>
Expand All @@ -50,7 +51,7 @@ class ResetPasswordController(userDao: UserDao, userService: UserService) extend
@RequestParam("code") formCode: String): ModelAndView = {
val user = userService.getUser(nick)

if (!userService.canResetPasswordByCode(user)) {
if (!userPermissionService.canResetPasswordByCode(user)) {
throw new AccessViolationException("Пароль этого пользователя нельзя сбросить")
}

Expand Down
57 changes: 4 additions & 53 deletions src/main/scala/ru/org/linux/user/UserService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ import com.typesafe.scalalogging.StrictLogging
import org.springframework.scala.transaction.support.TransactionManagement
import org.springframework.stereotype.Service
import org.springframework.transaction.PlatformTransactionManager
import ru.org.linux.auth.{AccessViolationException, AnySession, IPBlockDao}
import ru.org.linux.site.BadInputException
import ru.org.linux.auth.AccessViolationException
import ru.org.linux.spring.SiteConfig
import ru.org.linux.spring.dao.{DeleteInfoDao, UserAgentDao}
import ru.org.linux.spring.dao.UserAgentDao
import ru.org.linux.user.UserService.*
import ru.org.linux.util.image.{ImageInfo, ImageParam, ImageUtil}
import ru.org.linux.util.{BadImageException, StringUtil}
Expand Down Expand Up @@ -51,22 +50,14 @@ object UserService {

private val NameCacheSize = 10000

val MaxTotalInvites = 15
val MaxUserInvites = 1
val MaxInviteScoreLoss = 10
val InviteScore = 200

val MaxUnactivatedPerIp = 2

val CorrectorScore = 200

val MaxUserpicScoreLoss = 20
}

@Service
class UserService(siteConfig: SiteConfig, userDao: UserDao, ignoreListDao: IgnoreListDao,
userInvitesDao: UserInvitesDao, userLogDao: UserLogDao, deleteInfoDao: DeleteInfoDao,
ipBlockDao: IPBlockDao, userAgentDao: UserAgentDao, val transactionManager: PlatformTransactionManager)
userInvitesDao: UserInvitesDao, userLogDao: UserLogDao, userAgentDao: UserAgentDao,
val transactionManager: PlatformTransactionManager)
extends StrictLogging with TransactionManagement {
private val nameToIdCache =
CacheBuilder.newBuilder().maximumSize(UserService.NameCacheSize).build[String, Integer](
Expand Down Expand Up @@ -165,10 +156,6 @@ class UserService(siteConfig: SiteConfig, userDao: UserDao, ignoreListDao: Ignor
userLogDao.logSentPasswordReset(forUser, byUser, email)
}

def canResetPassword(user: User): Boolean = {
!userLogDao.hasRecentSelfEvent(user, Duration.ofDays(7), UserLogAction.SENT_PASSWORD_RESET)
}

def getUsersCached(ids: Iterable[Int]): Seq[User] = ids.map(x => userDao.getUserCached(x)).toSeq

def getUsersCachedMap(userIds: Iterable[Int]): Map[Int, User] =
Expand Down Expand Up @@ -234,16 +221,6 @@ class UserService(siteConfig: SiteConfig, userDao: UserDao, ignoreListDao: Ignor
throw new RuntimeException("Anonymous not found!?", e)
}

def canInvite(user: User): Boolean = user.isModerator || {
lazy val (totalInvites, userInvites) = userInvitesDao.countValidInvites(user)
lazy val userScoreLoss = deleteInfoDao.getRecentScoreLoss(user)

!user.isFrozen && user.getScore > InviteScore &&
totalInvites < MaxTotalInvites &&
userInvites < MaxUserInvites &&
userScoreLoss < MaxInviteScoreLoss
}

def createUser(nick: String, password: String, mail: InternetAddress, ip: String, invite: Option[String],
userAgent: Option[String], language: Option[String]): Int = {
val result = transactional() { _ =>
Expand Down Expand Up @@ -273,32 +250,9 @@ class UserService(siteConfig: SiteConfig, userDao: UserDao, ignoreListDao: Ignor
def getAllInvitedUsers(user: User): util.List[User] =
userInvitesDao.getAllInvitedUsers(user).map(userDao.getUserCached).asJava

def canRegister(remoteAddr: String): Boolean = !ipBlockDao.getBlockInfo(remoteAddr).isBlocked &&
userDao.countUnactivated(remoteAddr) < MaxUnactivatedPerIp

def wasRecentlyBlocker(user: User): Boolean =
userLogDao.hasRecentModerationEvent(user, Duration.ofDays(14), UserLogAction.BLOCK_USER)

def canLoadUserpic(user: User): Boolean = {
def userpicSetCount = userLogDao.getUserpicSetCount(user, Duration.ofHours(1))

def wasReset = userLogDao.hasRecentModerationEvent(user, Duration.ofDays(30), UserLogAction.RESET_USERPIC)

def userScoreLoss = deleteInfoDao.getRecentScoreLoss(user)

user.getScore >= 45 &&
!user.isFrozen &&
(userpicSetCount < 3) &&
!wasReset &&
(userScoreLoss < MaxUserpicScoreLoss)
}

def canEditProfileInfo(user: User): Boolean =
!user.isFrozen &&
!userLogDao.hasRecentModerationEvent(user, Duration.ofDays(1), UserLogAction.RESET_INFO) &&
!userLogDao.hasRecentModerationEvent(user, Duration.ofDays(1), UserLogAction.RESET_URL) &&
!userLogDao.hasRecentModerationEvent(user, Duration.ofDays(1), UserLogAction.RESET_TOWN)

def removeUserInfo(user: User, moderator: User): Unit = transactional() { _ =>
val userInfo = userDao.getUserInfo(user)

Expand Down Expand Up @@ -373,7 +327,4 @@ class UserService(siteConfig: SiteConfig, userDao: UserDao, ignoreListDao: Ignor

userDao.block(user, user, "самостоятельная блокировка аккаунта")
}

def canResetPasswordByCode(user: User): Boolean =
!user.isBlocked && user.isActivated && !user.isAnonymous && !user.isAdministrator
}
12 changes: 7 additions & 5 deletions src/main/scala/ru/org/linux/user/UserpicController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,21 @@ object UserpicController {
}

@Controller
class UserpicController(userDao: UserDao, siteConfig: SiteConfig, userService: UserService) extends StrictLogging {
class UserpicController(userDao: UserDao, siteConfig: SiteConfig, userService: UserService,
userPermissionService: UserPermissionService) extends StrictLogging {
@RequestMapping(value = Array("/addphoto.jsp"), method = Array(RequestMethod.GET))
def showForm: ModelAndView = AuthorizedOnly { currentUser =>
if (userService.canLoadUserpic(currentUser.user)) {
def showForm: ModelAndView = AuthorizedOnly { implicit currentUser =>
if (userPermissionService.canLoadUserpic) {
new ModelAndView("addphoto")
} else {
new ModelAndView("errors/code403")
}
}

@RequestMapping(value = Array("/addphoto.jsp"), method = Array(RequestMethod.POST))
def addPhoto(@RequestParam("file") file: MultipartFile, response: HttpServletResponse): ModelAndView = AuthorizedOnly { currentUser =>
if (!userService.canLoadUserpic(currentUser.user)) {
def addPhoto(@RequestParam("file") file: MultipartFile,
response: HttpServletResponse): ModelAndView = AuthorizedOnly { implicit currentUser =>
if (!userPermissionService.canLoadUserpic) {
throw new AccessViolationException("Forbidden")
}

Expand Down
Loading

0 comments on commit e88d26b

Please sign in to comment.