mirror of https://github.com/usbharu/Hideout.git
feat: Postのアクセス制御を追加
This commit is contained in:
parent
0825be76b3
commit
4fd97b6182
|
@ -16,10 +16,10 @@
|
|||
|
||||
package dev.usbharu.hideout.core.application.media
|
||||
|
||||
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
||||
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
|
||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||
import dev.usbharu.hideout.core.domain.model.media.*
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.FromApi
|
||||
import dev.usbharu.hideout.core.domain.shared.id.IdGenerateService
|
||||
import dev.usbharu.hideout.core.external.media.MediaProcessor
|
||||
import dev.usbharu.hideout.core.external.mediastore.MediaStore
|
||||
|
@ -35,11 +35,11 @@ class UploadMediaApplicationService(
|
|||
private val mediaRepository: MediaRepository,
|
||||
private val idGenerateService: IdGenerateService,
|
||||
transaction: Transaction
|
||||
) : AbstractApplicationService<UploadMedia, Media>(
|
||||
) : LocalUserAbstractApplicationService<UploadMedia, Media>(
|
||||
transaction,
|
||||
logger
|
||||
) {
|
||||
override suspend fun internalExecute(command: UploadMedia, principal: Principal): Media {
|
||||
override suspend fun internalExecute(command: UploadMedia, principal: FromApi): Media {
|
||||
val process = mediaProcessor.process(command.path, command.name, null)
|
||||
val id = idGenerateService.generateId()
|
||||
val thumbnailUri = if (process.thumbnailPath != null) {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package dev.usbharu.hideout.core.application.post
|
||||
|
||||
data class DeleteLocalPost(val postId: Long)
|
|
@ -16,24 +16,33 @@
|
|||
|
||||
package dev.usbharu.hideout.core.application.post
|
||||
|
||||
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
|
||||
import dev.usbharu.hideout.core.application.shared.LocalUserAbstractApplicationService
|
||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.FromApi
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class DeleteLocalPostApplicationService(
|
||||
private val postRepository: PostRepository,
|
||||
private val userDetailRepository: UserDetailRepository,
|
||||
private val actorRepository: ActorRepository,
|
||||
) {
|
||||
suspend fun delete(postId: Long, userDetailId: Long) {
|
||||
val findById = postRepository.findById(PostId(postId))!!
|
||||
val user = userDetailRepository.findById(UserDetailId(userDetailId))!!
|
||||
val actor = actorRepository.findById(user.actorId)!!
|
||||
private val actorRepository: ActorRepository, transaction: Transaction,
|
||||
) : LocalUserAbstractApplicationService<DeleteLocalPost, Unit>(transaction, logger) {
|
||||
|
||||
override suspend fun internalExecute(command: DeleteLocalPost, principal: FromApi) {
|
||||
val findById = postRepository.findById(PostId(command.postId))!!
|
||||
if (findById.actorId != principal.actorId) {
|
||||
throw PermissionDeniedException()
|
||||
}
|
||||
val actor = actorRepository.findById(principal.actorId)!!
|
||||
findById.delete(actor)
|
||||
postRepository.save(findById)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(DeleteLocalPostApplicationService::class.java)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,21 +16,29 @@
|
|||
|
||||
package dev.usbharu.hideout.core.application.post
|
||||
|
||||
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
|
||||
import dev.usbharu.hideout.core.application.shared.AbstractApplicationService
|
||||
import dev.usbharu.hideout.core.application.shared.Transaction
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class GetPostApplicationService(private val postRepository: PostRepository, transaction: Transaction) :
|
||||
class GetPostApplicationService(
|
||||
private val postRepository: PostRepository,
|
||||
private val iPostReadAccessControl: IPostReadAccessControl,
|
||||
transaction: Transaction
|
||||
) :
|
||||
AbstractApplicationService<GetPost, Post>(transaction, logger) {
|
||||
|
||||
override suspend fun internalExecute(command: GetPost, principal: Principal): Post {
|
||||
val post = postRepository.findById(PostId(command.postId)) ?: throw Exception("Post not found")
|
||||
|
||||
val post = postRepository.findById(PostId(command.postId)) ?: throw IllegalArgumentException("Post not found")
|
||||
if (iPostReadAccessControl.isAllow(post, principal).not()) {
|
||||
throw PermissionDeniedException()
|
||||
}
|
||||
return Post.of(post)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package dev.usbharu.hideout.core.domain.service.post
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Principal
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
interface IPostReadAccessControl {
|
||||
suspend fun isAllow(post: Post, principal: Principal): Boolean
|
||||
}
|
||||
|
||||
@Component
|
||||
class DefaultPostReadAccessControl(private val relationshipRepository: RelationshipRepository) :
|
||||
IPostReadAccessControl {
|
||||
override suspend fun isAllow(post: Post, principal: Principal): Boolean {
|
||||
val relationship = (relationshipRepository.findByActorIdAndTargetId(post.actorId, principal.actorId)
|
||||
?: Relationship.default(post.actorId, principal.actorId))
|
||||
|
||||
//ブロックされてたら見れない
|
||||
if (relationship.blocking) {
|
||||
return false
|
||||
}
|
||||
|
||||
//PublicかUnlistedなら見れる
|
||||
if (post.visibility == Visibility.PUBLIC || post.visibility == Visibility.UNLISTED) {
|
||||
return true
|
||||
}
|
||||
|
||||
//principalがAnonymousなら見れない
|
||||
if (principal is Anonymous) {
|
||||
return false
|
||||
}
|
||||
|
||||
//DirectでvisibleActorsに含まれていたら見れる
|
||||
if (post.visibility == Visibility.DIRECT && post.visibleActors.contains(principal.actorId)) {
|
||||
return true
|
||||
}
|
||||
|
||||
//Followersでフォロワーなら見れる
|
||||
if (post.visibility == Visibility.FOLLOWERS) {
|
||||
val inverseRelationship =
|
||||
relationshipRepository.findByActorIdAndTargetId(principal.actorId, post.actorId) ?: return false
|
||||
|
||||
return inverseRelationship.following
|
||||
}
|
||||
|
||||
//その他の場合は見れない
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
|
@ -14,8 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package dev.usbharu.hideout.core.domain.service.post
|
||||
package dev.usbharu.hideout.core.infrastructure.other
|
||||
|
||||
import dev.usbharu.hideout.core.domain.service.post.FormattedPostContent
|
||||
import dev.usbharu.hideout.core.domain.service.post.PostContentFormatter
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
|
@ -0,0 +1,44 @@
|
|||
package dev.usbharu.hideout.core.application.post
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.actor.TestActorFactory
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.TestPostFactory
|
||||
import dev.usbharu.hideout.core.domain.model.support.acct.Acct
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.FromApi
|
||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Spy
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import utils.TestTransaction
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class DeleteLocalPostApplicationServiceTest {
|
||||
@InjectMocks
|
||||
lateinit var service: DeleteLocalPostApplicationService
|
||||
|
||||
@Mock
|
||||
lateinit var postRepository: PostRepository
|
||||
|
||||
@Mock
|
||||
lateinit var actorRepository: ActorRepository
|
||||
|
||||
@Spy
|
||||
val transaction = TestTransaction
|
||||
|
||||
@Test
|
||||
fun Post主はローカルPostを削除できる() = runTest {
|
||||
whenever(postRepository.findById(PostId(1))).doReturn(TestPostFactory.create(actorId = 2))
|
||||
whenever(actorRepository.findById(ActorId(2))).doReturn(TestActorFactory.create(id = 2))
|
||||
|
||||
service.execute(DeleteLocalPost(1), FromApi(ActorId(2), UserDetailId(2), Acct("test", "example.com")))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package dev.usbharu.hideout.core.application.post
|
||||
|
||||
import dev.usbharu.hideout.core.application.exception.PermissionDeniedException
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostId
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.TestPostFactory
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||
import dev.usbharu.hideout.core.domain.service.post.IPostReadAccessControl
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Spy
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import utils.TestTransaction
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class GetPostApplicationServiceTest {
|
||||
@InjectMocks
|
||||
lateinit var service: GetPostApplicationService
|
||||
|
||||
@Mock
|
||||
lateinit var postRepository: PostRepository
|
||||
|
||||
@Mock
|
||||
lateinit var iPostReadAccessControl: IPostReadAccessControl
|
||||
|
||||
@Spy
|
||||
val transaction = TestTransaction
|
||||
|
||||
@Test
|
||||
fun postReadAccessControlがtrueを返したらPostが返ってくる() = runTest {
|
||||
val post = TestPostFactory.create(id = 1)
|
||||
whenever(postRepository.findById(PostId(1))).doReturn(post)
|
||||
whenever(iPostReadAccessControl.isAllow(any(), any())).doReturn(true)
|
||||
|
||||
val actual = service.execute(GetPost(1), Anonymous)
|
||||
assertEquals(Post.of(post), actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun postが見つからない場合失敗() = runTest {
|
||||
assertThrows<IllegalArgumentException> {
|
||||
service.execute(GetPost(2), Anonymous)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun postReadAccessControlがfalseを返したら失敗() = runTest {
|
||||
val post = TestPostFactory.create(id = 1)
|
||||
whenever(postRepository.findById(PostId(1))).doReturn(post)
|
||||
whenever(iPostReadAccessControl.isAllow(any(), any())).doReturn(false)
|
||||
assertThrows<PermissionDeniedException> {
|
||||
service.execute(GetPost(1), Anonymous)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
package dev.usbharu.hideout.core.domain.service.post
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorId
|
||||
import dev.usbharu.hideout.core.domain.model.post.TestPostFactory
|
||||
import dev.usbharu.hideout.core.domain.model.post.Visibility
|
||||
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||
import dev.usbharu.hideout.core.domain.model.support.acct.Acct
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
|
||||
import dev.usbharu.hideout.core.domain.model.support.principal.FromApi
|
||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailId
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.mockito.InjectMocks
|
||||
import org.mockito.Mock
|
||||
import org.mockito.junit.jupiter.MockitoExtension
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
|
||||
@ExtendWith(MockitoExtension::class)
|
||||
class DefaultPostReadAccessControlTest {
|
||||
@InjectMocks
|
||||
lateinit var service: DefaultPostReadAccessControl
|
||||
|
||||
@Mock
|
||||
lateinit var relationshipRepository: RelationshipRepository
|
||||
|
||||
@Test
|
||||
fun ブロックされてたら見れない() = runTest {
|
||||
whenever(relationshipRepository.findByActorIdAndTargetId(ActorId(1), ActorId(2))).doReturn(
|
||||
Relationship(
|
||||
actorId = ActorId(1),
|
||||
targetActorId = ActorId(2),
|
||||
following = false,
|
||||
blocking = true,
|
||||
muting = false,
|
||||
followRequesting = false,
|
||||
mutingFollowRequest = false,
|
||||
)
|
||||
)
|
||||
|
||||
val actual = service.isAllow(
|
||||
TestPostFactory.create(actorId = 1),
|
||||
FromApi(ActorId(2), UserDetailId(2), Acct("test", "example.com"))
|
||||
)
|
||||
|
||||
assertFalse(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun PublicかUnlistedなら見れる() = runTest {
|
||||
val actual = service.isAllow(TestPostFactory.create(visibility = Visibility.PUBLIC), Anonymous)
|
||||
assertTrue(actual)
|
||||
|
||||
val actual2 = service.isAllow(TestPostFactory.create(visibility = Visibility.UNLISTED), Anonymous)
|
||||
assertTrue(actual2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun FollowersかDirecのときAnonymousなら見れない() = runTest {
|
||||
val actual = service.isAllow(TestPostFactory.create(visibility = Visibility.FOLLOWERS), Anonymous)
|
||||
assertFalse(actual)
|
||||
|
||||
val actual2 = service.isAllow(TestPostFactory.create(visibility = Visibility.DIRECT), Anonymous)
|
||||
assertFalse(actual2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun DirectでvisibleActorsに含まれていたら見れる() = runTest {
|
||||
val actual = service.isAllow(
|
||||
TestPostFactory.create(actorId = 1, visibility = Visibility.DIRECT, visibleActors = listOf(2)),
|
||||
FromApi(ActorId(2), UserDetailId(2), Acct("test", "example.com"))
|
||||
)
|
||||
|
||||
assertTrue(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun DirectでvisibleActorsに含まれていなかったら見れない() = runTest {
|
||||
val actual = service.isAllow(
|
||||
TestPostFactory.create(actorId = 1, visibility = Visibility.DIRECT, visibleActors = listOf(3)),
|
||||
FromApi(ActorId(2), UserDetailId(2), Acct("test", "example.com"))
|
||||
)
|
||||
|
||||
assertFalse(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun Followersでフォロワーなら見れる() = runTest {
|
||||
whenever(relationshipRepository.findByActorIdAndTargetId(ActorId(1), ActorId(2))).doReturn(
|
||||
Relationship.default(
|
||||
actorId = ActorId(1),
|
||||
targetActorId = ActorId(2)
|
||||
)
|
||||
)
|
||||
whenever(relationshipRepository.findByActorIdAndTargetId(ActorId(2), ActorId(1))).doReturn(
|
||||
Relationship(
|
||||
actorId = ActorId(2),
|
||||
targetActorId = ActorId(1),
|
||||
following = true,
|
||||
blocking = false,
|
||||
muting = false,
|
||||
followRequesting = false,
|
||||
mutingFollowRequest = false
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
val actual = service.isAllow(
|
||||
TestPostFactory.create(actorId = 1, visibility = Visibility.FOLLOWERS),
|
||||
FromApi(ActorId(2), UserDetailId(2), Acct("test", "example.com"))
|
||||
)
|
||||
|
||||
assertTrue(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun relationshipが見つからない場合見れない() = runTest {
|
||||
val actual = service.isAllow(
|
||||
TestPostFactory.create(actorId = 1, visibility = Visibility.FOLLOWERS),
|
||||
FromApi(ActorId(2), UserDetailId(2), Acct("test", "example.com"))
|
||||
)
|
||||
|
||||
assertFalse(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun フォロワーじゃない場合は見れない() = runTest {
|
||||
whenever(relationshipRepository.findByActorIdAndTargetId(ActorId(1), ActorId(2))).doReturn(
|
||||
Relationship.default(
|
||||
actorId = ActorId(1),
|
||||
targetActorId = ActorId(2)
|
||||
)
|
||||
)
|
||||
whenever(relationshipRepository.findByActorIdAndTargetId(ActorId(2), ActorId(1))).doReturn(
|
||||
Relationship(
|
||||
actorId = ActorId(2),
|
||||
targetActorId = ActorId(1),
|
||||
following = false,
|
||||
blocking = false,
|
||||
muting = false,
|
||||
followRequesting = false,
|
||||
mutingFollowRequest = false
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
val actual = service.isAllow(
|
||||
TestPostFactory.create(actorId = 1, visibility = Visibility.FOLLOWERS),
|
||||
FromApi(ActorId(2), UserDetailId(2), Acct("test", "example.com"))
|
||||
)
|
||||
|
||||
assertFalse(actual)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue