Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #24114: Migrate user-management API to zio-json #643

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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