Big refactoring #77

Merged
flecomte merged 166 commits from refactoring-component-and-immutable into master 2021-03-24 19:06:07 +01:00
29 changed files with 195 additions and 124 deletions
Showing only changes of commit ce90884758 - Show all commits

View File

@@ -21,7 +21,11 @@ import fr.dcproject.component.comment.generic.routes.editComment
import fr.dcproject.component.comment.generic.routes.getChildrenComments
import fr.dcproject.component.comment.generic.routes.getOneComment
import fr.dcproject.elasticsearch.configElasticIndexes
import fr.dcproject.entity.User
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserRepository
import fr.dcproject.component.auth.routes.authLogin
import fr.dcproject.component.auth.routes.authRegister
import fr.dcproject.component.auth.routes.authSso
import fr.dcproject.event.EventNotification
import fr.dcproject.event.EventSubscriber
import fr.dcproject.routes.*
@@ -52,7 +56,6 @@ import org.slf4j.event.Level
import java.time.Duration
import java.util.*
import java.util.concurrent.CompletionException
import fr.dcproject.repository.User as UserRepository
fun main(args: Array<String>): Unit = io.ktor.server.jetty.EngineMain.main(args)
@@ -172,8 +175,11 @@ fun Application.module(env: Env = PROD) {
getOneComment(get(), get())
createCommentChildren(get(), get())
getChildrenComments(get(), get())
/* Auth */
authLogin(get())
authRegister(get())
authSso(get())
/* TODO */
auth(get(), get(), get())
constitution(get())
followArticle(get())
followConstitution(get())

View File

@@ -1,8 +1,8 @@
package fr.dcproject
import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.util.*

View File

@@ -4,7 +4,7 @@ import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.typesafe.config.ConfigFactory
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.UserI
import java.util.*
import java.net.URI

View File

@@ -18,7 +18,8 @@ import fr.dcproject.component.comment.generic.CommentVoter
import fr.dcproject.event.publisher.Publisher
import fr.dcproject.messages.Mailer
import fr.dcproject.messages.NotificationEmailSender
import fr.dcproject.messages.SsoManager
import fr.dcproject.component.auth.SsoManager
import fr.dcproject.component.auth.UserRepository
import fr.dcproject.repository.CommentConstitutionRepository
import fr.postgresjson.connexion.Connection
import fr.postgresjson.connexion.Requester
@@ -38,7 +39,6 @@ import fr.dcproject.repository.FollowArticle as FollowArticleRepository
import fr.dcproject.repository.FollowConstitution as FollowConstitutionRepository
import fr.dcproject.repository.OpinionArticle as OpinionArticleRepository
import fr.dcproject.repository.OpinionChoice as OpinionChoiceRepository
import fr.dcproject.repository.User as UserRepository
import fr.dcproject.repository.VoteArticle as VoteArticleRepository
import fr.dcproject.repository.VoteComment as VoteCommentRepository
import fr.dcproject.repository.VoteConstitution as VoteConstitutionRepository

View File

@@ -1,4 +1,4 @@
package fr.dcproject.messages
package fr.dcproject.component.auth
import com.sendgrid.helpers.mail.Mail
import com.sendgrid.helpers.mail.objects.Content
@@ -6,8 +6,12 @@ import com.sendgrid.helpers.mail.objects.Email
import fr.dcproject.JwtConfig
import fr.dcproject.component.citizen.CitizenBasicI
import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.messages.Mailer
import io.ktor.http.*
/**
* Send an email to the citizen with a link to automatically connect
*/
class SsoManager(
private val mailer: Mailer,
private val domain: String,
@@ -31,12 +35,18 @@ class SsoManager(
}
}
/**
* TODO pass token to the function to avoid double generations
*/
private fun generateHtmlContent(citizen: CitizenBasicI, url: String): String? {
val urlObject = URLBuilder(url)
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user))
return "Click <a href=\"${urlObject.buildString()}\">here</a> for connect to $domain"
}
/**
* TODO pass token to the function to avoid double generations
*/
private fun generateContent(citizen: CitizenBasicI, url: String): String {
val urlObject = URLBuilder(url)
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user))

View File

@@ -1,12 +1,11 @@
package fr.dcproject.entity
package fr.dcproject.component.auth
import fr.dcproject.entity.UserI.Roles
import fr.dcproject.component.auth.UserI.Roles
import fr.postgresjson.entity.*
import io.ktor.auth.*
import org.joda.time.DateTime
import java.util.*
@Deprecated("")
class User(
id: UUID = UUID.randomUUID(),
username: String,

View File

@@ -1,13 +1,12 @@
package fr.dcproject.repository
package fr.dcproject.component.auth
import fr.dcproject.entity.UserFull
import fr.postgresjson.connexion.Requester
import fr.postgresjson.repository.RepositoryI
import io.ktor.auth.UserPasswordCredential
import java.util.*
import fr.dcproject.entity.User as UserEntity
import fr.dcproject.component.auth.User as UserEntity
class User(override var requester: Requester) : RepositoryI {
class UserRepository(override var requester: Requester) : RepositoryI {
fun findByCredentials(credentials: UserPasswordCredential): UserEntity? {
return requester
.getFunction("check_user")

View File

@@ -0,0 +1,32 @@
package fr.dcproject.component.auth.routes
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import fr.dcproject.JwtConfig
import fr.dcproject.component.auth.UserRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
import io.ktor.locations.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.util.*
@KtorExperimentalLocationsAPI
@Location("/login")
private class LoginRequest
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
fun Route.authLogin(userRepo: UserRepository) {
post<LoginRequest> {
try {
val credentials = call.receive<UserPasswordCredential>()
userRepo.findByCredentials(credentials)?.let { user ->
call.respondText(JwtConfig.makeToken(user))
} ?: call.respond(HttpStatusCode.BadRequest, "Username not exist or password is wrong")
} catch (e: MismatchedInputException) {
call.respond(HttpStatusCode.BadRequest, "You must be send name and password to the request")
}
}
}

View File

@@ -0,0 +1,69 @@
package fr.dcproject.component.auth.routes
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import fr.dcproject.JwtConfig
import fr.dcproject.component.auth.routes.RegisterRequest.Input
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.locations.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.util.*
import org.joda.time.DateTime
@KtorExperimentalLocationsAPI
@Location("/register")
private class RegisterRequest {
data class Input(
val name: Name,
val email: String,
val birthday: DateTime,
val voteAnonymous: Boolean = true,
val followAnonymous: Boolean = true,
val user: User
) {
data class Name(
val firstName: String,
val lastName: String,
val civility: String? = null
)
data class User(
val username: String,
val plainPassword: String? = null
)
}
}
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
fun Route.authRegister(citizenRepo: CitizenRepository) {
fun Input.toCitizen(): Citizen = Citizen(
name = CitizenI.Name(name.firstName, name.lastName, name.civility),
birthday = birthday,
email = email,
followAnonymous = followAnonymous,
voteAnonymous = voteAnonymous,
user = User(
username = user.username,
plainPassword = user.plainPassword,
roles = listOf(UserI.Roles.ROLE_USER)
)
)
post<RegisterRequest> {
try {
val citizen = call.receive<Input>().toCitizen()
val createdCitizen = citizenRepo.insertWithUser(citizen)?.user ?: throw BadRequestException("Bad request")
call.respondText(JwtConfig.makeToken(createdCitizen))
} catch (e: MissingKotlinParameterException) {
call.respond(HttpStatusCode.BadRequest)
}
}
}

View File

@@ -0,0 +1,35 @@
package fr.dcproject.component.auth.routes
import fr.dcproject.component.auth.routes.SsoRequest.Input
import fr.dcproject.component.auth.SsoManager
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.locations.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.util.*
@KtorExperimentalLocationsAPI
@Location("/sso")
private class SsoRequest {
data class Input(val email: String, val url: String)
}
/**
* Send an email to the citizen with a link to automatically connect
*/
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
fun Route.authSso(ssoManager: SsoManager) {
post<SsoRequest> {
call.receive<Input>().run {
try {
ssoManager.sendEmail(email, url)
} catch (e: SsoManager.EmailNotFound) {
call.respond(HttpStatusCode.NotFound)
}
call.respond(HttpStatusCode.NoContent)
}
}
}

View File

@@ -1,15 +1,14 @@
package fr.dcproject.component.citizen
import fr.dcproject.component.citizen.CitizenI.Name
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.entity.UserRef
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.component.auth.UserRef
import fr.dcproject.entity.WorkgroupSimple
import fr.postgresjson.entity.*
import org.joda.time.DateTime
import java.util.*
@Deprecated("")
class Citizen(
override val id: UUID = UUID.randomUUID(),
override val name: Name,

View File

@@ -1,6 +1,6 @@
package fr.dcproject.component.citizen
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.UserI
import fr.postgresjson.connexion.Paginated
import fr.postgresjson.connexion.Requester
import fr.postgresjson.repository.RepositoryI

View File

@@ -5,7 +5,7 @@ import fr.dcproject.citizen
import fr.dcproject.citizenOrNull
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenVoter
import fr.dcproject.repository.User
import fr.dcproject.component.auth.UserRepository
import fr.dcproject.voter.assert
import io.ktor.application.*
import io.ktor.auth.*
@@ -23,7 +23,7 @@ class ChangePasswordCitizenRequest(val citizen: Citizen) {
}
@KtorExperimentalLocationsAPI
fun Route.changeMyPassword(voter: CitizenVoter, userRepository: User) {
fun Route.changeMyPassword(voter: CitizenVoter, userRepository: UserRepository) {
put<ChangePasswordCitizenRequest> {
voter.assert { canChangePassword(it.citizen, citizenOrNull) }
try {

View File

@@ -1,5 +1,6 @@
package fr.dcproject.entity
import fr.dcproject.component.auth.UserI
import fr.dcproject.component.citizen.CitizenBasicI
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenWithUserI

View File

@@ -1,80 +0,0 @@
package fr.dcproject.routes
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException
import fr.dcproject.JwtConfig
import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.entity.UserI.Roles.ROLE_USER
import fr.dcproject.messages.SsoManager
import fr.dcproject.routes.AuthPaths.LoginRequest
import fr.dcproject.routes.AuthPaths.RegisterRequest
import fr.dcproject.routes.AuthPaths.SsoRequest
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.locations.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.util.*
import fr.dcproject.component.citizen.Citizen as CitizenEntity
import fr.dcproject.repository.User as UserRepository
@KtorExperimentalLocationsAPI
object AuthPaths {
@Location("/login")
class LoginRequest
@Location("/register")
class RegisterRequest
@Location("/sso")
class SsoRequest {
data class Content(val email: String, val url: String)
}
}
@KtorExperimentalLocationsAPI
@KtorExperimentalAPI
fun Route.auth(
userRepo: UserRepository,
citizenRepo: CitizenRepository,
ssoManager: SsoManager
) {
post<LoginRequest> {
try {
val credentials = call.receive<UserPasswordCredential>()
val user = userRepo.findByCredentials(credentials) ?: throw WrongLoginOrPassword()
call.respondText(JwtConfig.makeToken(user))
} catch (e: MismatchedInputException) {
call.respond(HttpStatusCode.BadRequest, "You must be send name and password to the request")
} catch (e: WrongLoginOrPassword) {
call.respond(HttpStatusCode.BadRequest, e.message)
}
}
post<RegisterRequest> {
try {
val citizen = call.receive<CitizenEntity>()
citizen.user.roles = listOf(ROLE_USER)
val created = citizenRepo.insertWithUser(citizen)?.user ?: throw BadRequestException("Bad request")
call.respondText(JwtConfig.makeToken(created))
} catch (e: MissingKotlinParameterException) {
call.respond(HttpStatusCode.BadRequest)
}
}
post<SsoRequest> {
val content = call.receive<SsoRequest.Content>()
try {
ssoManager.sendEmail(content.email, content.url)
} catch (e: SsoManager.EmailNotFound) {
call.respond(HttpStatusCode.NotFound)
}
call.respond(HttpStatusCode.NoContent)
}
}
class WrongLoginOrPassword(override val message: String = "Username not exist or password is wrong") : Exception()

View File

@@ -2,7 +2,7 @@ package fr.dcproject.security.voter
import fr.dcproject.component.comment.generic.CommentForView
import fr.dcproject.entity.ConstitutionSimple
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.UserI
import fr.dcproject.user
import fr.dcproject.voter.NoRuleDefinedException
import fr.dcproject.voter.NoSubjectDefinedException

View File

@@ -1,6 +1,6 @@
package fr.dcproject.security.voter
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.UserI
import fr.dcproject.entity.WorkgroupI
import fr.dcproject.entity.WorkgroupWithAuthI
import fr.dcproject.entity.WorkgroupWithMembersI.Member.Role

View File

@@ -16,7 +16,7 @@ import org.joda.time.DateTime
import org.koin.test.KoinTest
import org.koin.test.get
import java.util.*
import fr.dcproject.entity.User as UserEntity
import fr.dcproject.component.auth.User as UserEntity
class ArticleSteps : En, KoinTest {
init {

View File

@@ -3,7 +3,7 @@ package steps
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenRepository
import fr.dcproject.entity.User
import fr.dcproject.component.auth.User
import io.cucumber.datatable.DataTable
import io.cucumber.java8.En
import org.joda.time.DateTime

View File

@@ -16,7 +16,7 @@ import org.joda.time.DateTime
import org.koin.test.KoinTest
import org.koin.test.get
import java.util.*
import fr.dcproject.entity.User as UserEntity
import fr.dcproject.component.auth.User as UserEntity
import fr.dcproject.repository.Constitution as ConstitutionRepository
class ConstitutionSteps : En, KoinTest {

View File

@@ -1,5 +1,6 @@
package steps
import fr.dcproject.component.auth.User
import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenRef

View File

@@ -4,8 +4,8 @@ import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.article.ArticleVoter
import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.postgresjson.connexion.Paginated

View File

@@ -3,8 +3,8 @@ package unit.voter
import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.citizen.CitizenVoter
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import io.mockk.mockkStatic

View File

@@ -8,8 +8,8 @@ import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.component.comment.generic.CommentForUpdate
import fr.dcproject.component.comment.generic.CommentForView
import fr.dcproject.component.comment.generic.CommentVoter
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.voter.Vote.DENIED
import fr.dcproject.voter.Vote.GRANTED
import fr.postgresjson.connexion.Paginated

View File

@@ -7,8 +7,8 @@ import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.Follow
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.security.voter.FollowVoter
import fr.dcproject.voter.NoSubjectDefinedException
import fr.ktorVoter.ActionI

View File

@@ -5,8 +5,8 @@ import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.OpinionChoice
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.security.voter.OpinionChoiceVoter
import fr.dcproject.user
import fr.ktorVoter.ActionI

View File

@@ -6,8 +6,8 @@ import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.Opinion
import fr.dcproject.entity.OpinionChoice
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.security.voter.OpinionVoter
import fr.dcproject.user
import fr.dcproject.voter.NoSubjectDefinedException

View File

@@ -7,8 +7,8 @@ import fr.dcproject.component.citizen.Citizen
import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.entity.VoteForUpdate
import fr.dcproject.security.voter.VoteVoter
import fr.dcproject.voter.NoSubjectDefinedException

View File

@@ -4,8 +4,8 @@ import fr.dcproject.component.article.ArticleForView
import fr.dcproject.component.citizen.CitizenBasic
import fr.dcproject.component.citizen.CitizenCart
import fr.dcproject.component.citizen.CitizenI
import fr.dcproject.entity.User
import fr.dcproject.entity.UserI
import fr.dcproject.component.auth.User
import fr.dcproject.component.auth.UserI
import fr.dcproject.entity.WorkgroupRef
import fr.dcproject.entity.WorkgroupWithMembersI
import fr.dcproject.security.voter.WorkgroupVoter