Skip to content

Commit

Permalink
Fixes #24114: Migrate user-management API to zio-json
Browse files Browse the repository at this point in the history
  • Loading branch information
clarktsiory authored and fanf committed Feb 5, 2024
1 parent 9de74b9 commit 5eedb39
Show file tree
Hide file tree
Showing 11 changed files with 793 additions and 186 deletions.
6 changes: 6 additions & 0 deletions user-management/pom-template.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@

<dependencies>
<!-- Add other plugin specific dependencies -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<!-- Below is an horrible if/then/else in maven. You shouldn't have anything to change here -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
package bootstrap.rudder.plugin

import bootstrap.liftweb.RudderConfig
import bootstrap.liftweb.UserFileProcessing
import com.normation.plugins.PluginStatus
import com.normation.plugins.RudderPluginModule
import com.normation.plugins.usermanagement.CheckRudderPluginEnableImpl
Expand Down Expand Up @@ -66,9 +67,9 @@ object UserManagementConf extends RudderPluginModule {

lazy val api = new UserManagementApiImpl(
RudderConfig.userRepository,
RudderConfig.restExtractorService,
RudderConfig.rudderUserListProvider,
new UserManagementService(RudderConfig.userRepository)
RudderConfig.authenticationProviders,
new UserManagementService(RudderConfig.userRepository, UserFileProcessing.getUserResourceFile())
)

RudderConfig.userAuthorisationLevel.overrideLevel(new UserManagementAuthorizationLevel(pluginStatusService))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,17 @@

package com.normation.plugins.usermanagement

import bootstrap.liftweb.AuthBackendProvidersManager
import bootstrap.liftweb.PasswordEncoder
import bootstrap.liftweb.RudderConfig
import bootstrap.liftweb.ValidatedUserList
import com.normation.rudder.Role
import com.normation.rudder.Role.Custom
import com.normation.rudder.RudderRoles
import com.normation.zio._
import io.scalaland.chimney.Transformer
import net.liftweb.common.Logger
import net.liftweb.json.{Serialization => S}
import net.liftweb.json.JsonAST.JValue
import org.slf4j.LoggerFactory
import zio.json._

/**
* Applicative log of interest for Rudder ops.
Expand All @@ -57,22 +58,38 @@ object UserManagementLogger extends Logger {

object Serialisation {

implicit val jsonUserFormDataDecoder: JsonDecoder[JsonUserFormData] = DeriveJsonDecoder.gen[JsonUserFormData]
implicit val jsonRoleAuthorizationsDecoder: JsonDecoder[JsonRoleAuthorizations] = DeriveJsonDecoder.gen[JsonRoleAuthorizations]

implicit val jsonUserEncoder: JsonEncoder[JsonUser] = DeriveJsonEncoder.gen[JsonUser]
implicit val jsonAuthConfigEncoder: JsonEncoder[JsonAuthConfig] = DeriveJsonEncoder.gen[JsonAuthConfig]
implicit val jsonRoleEncoder: JsonEncoder[JsonRole] = DeriveJsonEncoder.gen[JsonRole]
implicit val jsonInternalUserDataEncoder: JsonEncoder[JsonInternalUserData] = DeriveJsonEncoder.gen[JsonInternalUserData]
implicit val jsonAddedUserEncoder: JsonEncoder[JsonAddedUser] = DeriveJsonEncoder.gen[JsonAddedUser]
implicit val jsonUpdatedUserEncoder: JsonEncoder[JsonUpdatedUser] = DeriveJsonEncoder.gen[JsonUpdatedUser]
implicit val jsonUsernameEncoder: JsonEncoder[JsonUsername] = DeriveJsonEncoder.gen[JsonUsername]
implicit val jsonDeletedUserEncoder: JsonEncoder[JsonDeletedUser] = DeriveJsonEncoder.gen[JsonDeletedUser]
implicit val jsonReloadStatusEncoder: JsonEncoder[JsonReloadStatus] = DeriveJsonEncoder.gen[JsonReloadStatus]
implicit val jsonReloadResultEncoder: JsonEncoder[JsonReloadResult] = DeriveJsonEncoder.gen[JsonReloadResult]
implicit val jsonRoleCoverageEncoder: JsonEncoder[JsonRoleCoverage] = DeriveJsonEncoder.gen[JsonRoleCoverage]
implicit val jsonCoverageEncoder: JsonEncoder[JsonCoverage] = DeriveJsonEncoder.gen[JsonCoverage]

implicit class AuthConfigSer(auth: ValidatedUserList) {
def toJson: JValue = {
def serialize(implicit authProviderManager: AuthBackendProvidersManager): JsonAuthConfig = {
val encoder: String = PassEncoderToString(auth)
val authBackendsProvider = RudderConfig.authenticationProviders.getConfiguredProviders().map(_.name).toSet
val authBackendsProvider = authProviderManager.getConfiguredProviders().map(_.name).toSet

// for now, we can only guess if the role list can be extended/overridden (and only guess for the worse).
// The correct solution is to get that from rudder, but it will be done along with other enhancement about
// user / roles management.
// Also, until then, we need to update that test is other backend get that possibility
val roleListOverride = if(authBackendsProvider.contains("oidc") || authBackendsProvider.contains("oauth2")) {
val roleListOverride = if (authBackendsProvider.contains("oidc") || authBackendsProvider.contains("oauth2")) {
"override" // should be a type provided by rudder core
} else {
"none"
}

val jUser = auth.users.map {
val jUser = auth.users.map {
case (_, u) =>
val (rs, custom) = {
UserManagementService
Expand All @@ -89,10 +106,8 @@ object Serialisation {
rs.map(_.name)
)
}.toList.sortBy(_.login)
val json = JsonAuthConfig(encoder, roleListOverride, authBackendsProvider, jUser)
import net.liftweb.json._
implicit val formats = S.formats(NoTypeHints)
Extraction.decompose(json)
val json = JsonAuthConfig(encoder, roleListOverride, authBackendsProvider, jUser)
json
}
}

Expand Down Expand Up @@ -120,3 +135,89 @@ final case class JsonUser(
authz: Set[String],
permissions: Set[String]
)

final case class JsonRole(
@jsonField("id") name: String,
rights: List[String]
)

final case class JsonReloadResult(reload: JsonReloadStatus)

object JsonReloadResult {
val Done = JsonReloadResult(JsonReloadStatus("Done"))
}
final case class JsonReloadStatus(status: String)

final case class JsonInternalUserData(
username: String,
password: String,
permissions: List[String]
)

object JsonInternalUserData {
implicit val transformer: Transformer[User, JsonInternalUserData] = Transformer.derive[User, JsonInternalUserData]
}

final case class JsonAddedUser(
addedUser: JsonInternalUserData
) extends AnyVal
object JsonAddedUser {
implicit val transformer: Transformer[User, JsonAddedUser] = Transformer.derive[User, JsonAddedUser]
}

final case class JsonUpdatedUser(
updatedUser: JsonInternalUserData
) extends AnyVal
object JsonUpdatedUser {
implicit val transformer: Transformer[User, JsonUpdatedUser] = Transformer.derive[User, JsonUpdatedUser]
}

final case class JsonUsername(
username: String
)

final case class JsonDeletedUser(
deletedUser: JsonUsername
) extends AnyVal
object JsonDeletedUser {
implicit val usernameTransformer: Transformer[String, JsonUsername] = JsonUsername(_)
implicit val transformer: Transformer[String, JsonDeletedUser] = Transformer.derive[String, JsonDeletedUser]
}

final case class JsonUserFormData(
username: String,
password: String,
permissions: List[String],
isPreHashed: Boolean
)

object JsonUserFormData {
implicit val transformer: Transformer[JsonUserFormData, User] = Transformer.derive[JsonUserFormData, User]
}

final case class JsonCoverage(
coverage: JsonRoleCoverage
) extends AnyVal
object JsonCoverage {
implicit val transformer: Transformer[(Set[Role], Set[Custom]), JsonCoverage] =
Transformer.derive[(Set[Role], Set[Custom]), JsonCoverage]
}

final case class JsonRoleCoverage(
permissions: Set[String],
custom: List[String]
)

object JsonRoleCoverage {
implicit private[JsonRoleCoverage] val roleTransformer: Transformer[Role, String] = _.name
implicit private[JsonRoleCoverage] val customRolesTransformer: Transformer[Set[Custom], List[String]] =
_.flatMap(_.rights.authorizationTypes.map(_.id)).toList.sorted

implicit val transformer: Transformer[(Set[Role], Set[Custom]), JsonRoleCoverage] =
Transformer.derive[(Set[Role], Set[Custom]), JsonRoleCoverage]
}

final case class JsonRoleAuthorizations(
permissions: List[String],
authz: List[String]
)

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,11 @@ object UserManagementIO {
}
}

def getUserFilePath: IOResult[File] = {
val resources: IOResult[UserFile] = UserFileProcessing.getUserResourceFile()
resources.map { r =>
if (r.name.startsWith("classpath:"))
File(new CPResource(UserFileProcessing.DEFAULT_AUTH_FILE_NAME).getPath)
else
File(r.name)
}
def getUserFilePath(resourceFile: UserFile): File = {
if (resourceFile.name.startsWith("classpath:"))
File(new CPResource(UserFileProcessing.DEFAULT_AUTH_FILE_NAME).getPath)
else
File(resourceFile.name)
}
}

Expand Down Expand Up @@ -196,7 +193,7 @@ object UserManagementService {

}

class UserManagementService(userRepository: UserRepository) {
class UserManagementService(userRepository: UserRepository, getUserResourceFile: IOResult[UserFile]) {
import UserManagementService._

/*
Expand All @@ -205,7 +202,7 @@ class UserManagementService(userRepository: UserRepository) {
*/
def add(newUser: User, isPreHashed: Boolean): IOResult[User] = {
for {
file <- getUserFilePath
file <- getUserResourceFile.map(getUserFilePath(_))
parsedFile <- IOResult.attempt(ConstructingParser.fromFile(file.toJava, preserveWS = true))
userXML <- IOResult.attempt(parsedFile.document().children)
user <- (userXML \\ "authentication").head match {
Expand Down Expand Up @@ -234,7 +231,7 @@ class UserManagementService(userRepository: UserRepository) {
*/
def remove(toDelete: String, actor: EventActor): IOResult[Unit] = {
for {
file <- getUserFilePath
file <- getUserResourceFile.map(getUserFilePath(_))
parsedFile <- IOResult.attempt(ConstructingParser.fromFile(file.toJava, preserveWS = true))
userXML <- IOResult.attempt(parsedFile.document().children)
toUpdate = (userXML \\ "authentication").head
Expand All @@ -254,7 +251,7 @@ class UserManagementService(userRepository: UserRepository) {
*/
def update(currentUser: String, newUser: User, isPreHashed: Boolean): IOResult[Unit] = {
for {
file <- getUserFilePath
file <- getUserResourceFile.map(getUserFilePath(_))
parsedFile <- IOResult.attempt(ConstructingParser.fromFile(file.toJava, preserveWS = true))
userXML <- IOResult.attempt(parsedFile.document().children)
toUpdate = (userXML \\ "authentication").head
Expand Down Expand Up @@ -285,7 +282,7 @@ class UserManagementService(userRepository: UserRepository) {

def getAll: IOResult[UserFileInfo] = {
for {
file <- getUserFilePath
file <- getUserResourceFile.map(getUserFilePath(_))
parsedFile <- IOResult.attempt(ConstructingParser.fromFile(file.toJava, preserveWS = true))
userXML <- IOResult.attempt(parsedFile.document().children)
res <- (userXML \\ "authentication").head match {
Expand Down
Loading

0 comments on commit 5eedb39

Please sign in to comment.