Fix security

This commit is contained in:
2020-10-04 01:10:22 +02:00
parent 317e029f79
commit 74923891d0
6 changed files with 48 additions and 22 deletions

View File

@@ -18,6 +18,8 @@
<package name="" alias="true" withSubpackages="true" /> <package name="" alias="true" withSubpackages="true" />
</value> </value>
</option> </option>
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<PostgresCodeStyleSettings version="2"> <PostgresCodeStyleSettings version="2">
@@ -25,6 +27,7 @@
<option name="KEYWORD_CASE" value="1" /> <option name="KEYWORD_CASE" value="1" />
<option name="IDENTIFIER_CASE" value="1" /> <option name="IDENTIFIER_CASE" value="1" />
<option name="TYPE_CASE" value="1" /> <option name="TYPE_CASE" value="1" />
<option name="CUSTOM_TYPE_CASE" value="1" />
<option name="ALIAS_CASE" value="1" /> <option name="ALIAS_CASE" value="1" />
</PostgresCodeStyleSettings> </PostgresCodeStyleSettings>
<codeStyleSettings language="kotlin"> <codeStyleSettings language="kotlin">

3
.idea/misc.xml generated
View File

@@ -10,4 +10,7 @@
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="corretto-11" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="corretto-11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
<component name="TaskProjectConfiguration">
<server type="GitHub" url="https://github.com" />
</component>
</project> </project>

View File

@@ -36,9 +36,11 @@ class ArticleForUpdate(
tags: List<String> = emptyList(), tags: List<String> = emptyList(),
val draft: Boolean = false, val draft: Boolean = false,
val createdBy: CitizenRef, val createdBy: CitizenRef,
val workgroup: WorkgroupRef? = null val workgroup: WorkgroupRef? = null,
) : ArticleRefVersioning(id) { versionId: UUID?
) : ArticleRefVersioning(id, versionId = versionId ?: UUID.randomUUID()) {
val tags: List<String> = tags.distinct() val tags: List<String> = tags.distinct()
val isNew = versionId == null
} }
open class ArticleSimple( open class ArticleSimple(

View File

@@ -11,6 +11,7 @@ import fr.dcproject.event.raiseEvent
import fr.dcproject.repository.Article.Filter import fr.dcproject.repository.Article.Filter
import fr.dcproject.repository.Workgroup as WorkgroupRepository import fr.dcproject.repository.Workgroup as WorkgroupRepository
import fr.dcproject.security.voter.ArticleVoter.Action.CREATE import fr.dcproject.security.voter.ArticleVoter.Action.CREATE
import fr.dcproject.security.voter.ArticleVoter.Action.UPDATE
import fr.dcproject.security.voter.ArticleVoter.Action.VIEW import fr.dcproject.security.voter.ArticleVoter.Action.VIEW
import fr.dcproject.views.ArticleViewManager import fr.dcproject.views.ArticleViewManager
import fr.ktorVoter.assertCan import fr.ktorVoter.assertCan
@@ -81,18 +82,17 @@ object ArticlesPaths {
suspend fun getNewArticle(call: ApplicationCall): ArticleForUpdate = call.receive<Article>().run { suspend fun getNewArticle(call: ApplicationCall): ArticleForUpdate = call.receive<Article>().run {
ArticleForUpdate( ArticleForUpdate(
id ?: UUID.randomUUID(), id = id ?: UUID.randomUUID(),
title, title = title,
anonymous, anonymous = anonymous,
content, content = content,
description, description = description,
tags, tags = tags,
draft, draft = draft,
createdBy = call.citizen, createdBy = call.citizen,
workgroup = if (workgroup != null) workgroupRepository.findById(workgroup.id) as WorkgroupSimple<CitizenRef> else null workgroup = if (workgroup != null) workgroupRepository.findById(workgroup.id) as WorkgroupSimple<CitizenRef> else null,
).also { versionId = versionId
it.versionId = versionId ?: UUID.randomUUID() )
}
} }
} }
} }
@@ -134,7 +134,11 @@ fun Route.article(repo: ArticleRepository, viewManager: ArticleViewManager) {
post<ArticlesPaths.PostArticleRequest> { post<ArticlesPaths.PostArticleRequest> {
it.getNewArticle(call).also { article -> it.getNewArticle(call).also { article ->
assertCan(CREATE, article) if(article.isNew) {
assertCan(CREATE, article)
} else {
assertCan(UPDATE, article)
}
val newArticle = repo.upsert(article) ?: error("Article not updated") val newArticle = repo.upsert(article) ?: error("Article not updated")
call.respond(article) call.respond(article)
raiseEvent(ArticleUpdate.event, ArticleUpdate(newArticle)) raiseEvent(ArticleUpdate.event, ArticleUpdate(newArticle))

View File

@@ -1,18 +1,26 @@
package fr.dcproject.security.voter package fr.dcproject.security.voter
import fr.dcproject.citizenOrNull
import fr.dcproject.entity.ArticleAuthI import fr.dcproject.entity.ArticleAuthI
import fr.dcproject.entity.ArticleForUpdate
import fr.dcproject.entity.ArticleI import fr.dcproject.entity.ArticleI
import fr.dcproject.entity.Citizen as CitizenEntity
import fr.dcproject.entity.CitizenI
import fr.dcproject.entity.UserI import fr.dcproject.entity.UserI
import fr.dcproject.repository.Article as ArticleRepo
import fr.dcproject.user import fr.dcproject.user
import fr.ktorVoter.ActionI import fr.ktorVoter.ActionI
import fr.ktorVoter.Vote import fr.ktorVoter.Vote
import fr.ktorVoter.Voter import fr.ktorVoter.Voter
import fr.ktorVoter.checkClass import fr.ktorVoter.checkClass
import io.ktor.application.ApplicationCall import io.ktor.application.ApplicationCall
import org.koin.core.KoinComponent
import org.koin.core.inject
import fr.dcproject.entity.Comment as CommentEntity import fr.dcproject.entity.Comment as CommentEntity
import fr.dcproject.entity.Vote as VoteEntity import fr.dcproject.entity.Vote as VoteEntity
class ArticleVoter : Voter { class ArticleVoter : Voter, KoinComponent {
private val articleRepo: ArticleRepo by inject()
enum class Action : ActionI { enum class Action : ActionI {
CREATE, CREATE,
UPDATE, UPDATE,
@@ -30,7 +38,7 @@ class ArticleVoter : Voter {
if (action == Action.CREATE && user is UserI) return Vote.GRANTED if (action == Action.CREATE && user is UserI) return Vote.GRANTED
if (action == Action.VIEW) return view(subject, user) if (action == Action.VIEW) return view(subject, user)
if (action == Action.DELETE) return delete(subject, user) if (action == Action.DELETE) return delete(subject, user)
if (action == Action.UPDATE) return update(subject, user) if (action == Action.UPDATE) return update(subject, call.citizenOrNull)
if (action is CommentVoter.Action) return voteForComment(action, subject) if (action is CommentVoter.Action) return voteForComment(action, subject)
if (action is VoteVoter.Action) return voteForVote(action, subject) if (action is VoteVoter.Action) return voteForVote(action, subject)
if (action is Action) return Vote.DENIED if (action is Action) return Vote.DENIED
@@ -58,11 +66,16 @@ class ArticleVoter : Voter {
return Vote.DENIED return Vote.DENIED
} }
private fun update(subject: Any?, user: UserI?): Vote { private fun update(subject: Any?, citizen: CitizenEntity?): Vote {
checkClass(ArticleAuthI::class, subject) checkClass(ArticleForUpdate::class, subject)
if (subject is ArticleAuthI<*>) { if (subject is ArticleForUpdate) {
if (user is UserI && subject.createdBy.user.id == user.id) { /* The new Article must by created by the same citizen of the connected citizen */
return Vote.GRANTED if (citizen is CitizenI && subject.createdBy.id == citizen.id) {
/* The creator must be the same of the creator of preview version of article */
if(articleRepo.findVerionsByVersionsId(1, 1, subject.versionId).result.first().createdBy.id == citizen.id) {
return Vote.GRANTED
}
return Vote.DENIED
} }
} }
return Vote.DENIED return Vote.DENIED

View File

@@ -73,7 +73,8 @@ class ArticleSteps : En, KoinTest {
content = "bla bla bla", content = "bla bla bla",
description = "A super article", description = "A super article",
createdBy = createdBy, createdBy = createdBy,
workgroup = workgroup workgroup = workgroup,
versionId = UUID.randomUUID()
) )
get<ArticleRepository>().upsert(article) get<ArticleRepository>().upsert(article)
} }