Skip to content

Commit

Permalink
Merge pull request #668 from clarktsiory/bug_24219/display_user_detai…
Browse files Browse the repository at this point in the history
…l_using_information_from_database

Fixes #24219: Display user detail using information from database
  • Loading branch information
fanf committed Feb 29, 2024
2 parents bc9a4f4 + 6627212 commit 8f6ae6d
Show file tree
Hide file tree
Showing 17 changed files with 1,354 additions and 487 deletions.
36 changes: 35 additions & 1 deletion user-management/src/main/elm/sources/ApiCalls.elm
Expand Up @@ -6,10 +6,12 @@ module ApiCalls exposing (..)
-- API call to get the category tree


import DataTypes exposing (AddUserForm, Model, Msg(..))
import DataTypes exposing (AddUserForm, Model, Msg(..), Username)
import Http exposing (emptyBody, expectJson, jsonBody, request, send)
import JsonDecoder exposing (decodeApiAddUserResult, decodeApiCurrentUsersConf, decodeApiDeleteUserResult, decodeApiReloadResult, decodeApiUpdateUserResult, decodeGetRoleApiResult)
import JsonEncoder exposing (encodeAddUser)
import JsonDecoder exposing (decodeApiStatusResult)
import Json.Decode as Decode

getUrl: DataTypes.Model -> String -> String
getUrl m url =
Expand Down Expand Up @@ -96,6 +98,38 @@ updateUser model toUpdate userForm =
in
send UpdateUser req

activateUser : Model -> Username -> Cmd Msg
activateUser model username =
let
req =
request
{ method = "PUT"
, headers = []
, url = getUrl model ("/usermanagement/status/activate/" ++ username)
, body = emptyBody
, expect = expectJson (Decode.map (\_ -> username) decodeApiStatusResult)
, timeout = Nothing
, withCredentials = False
}
in
send UpdateUserStatus req

disableUser : Model -> Username -> Cmd Msg
disableUser model username =
let
req =
request
{ method = "PUT"
, headers = []
, url = getUrl model ("/usermanagement/status/disable/" ++ username)
, body = emptyBody
, expect = expectJson (Decode.map (\_ -> username) decodeApiStatusResult)
, timeout = Nothing
, withCredentials = False
}
in
send UpdateUserStatus req

getRoleConf : Model -> Cmd Msg
getRoleConf model =
let
Expand Down
87 changes: 79 additions & 8 deletions user-management/src/main/elm/sources/DataTypes.elm
Expand Up @@ -8,12 +8,14 @@ import Toasty.Defaults

type alias Roles = Dict String (List String)
type alias Users = Dict Username User
type alias Provider = String
type alias Username = String
type alias Password = String
type alias RoleConf = List Role
type alias ProvidersInfo = Dict Provider ProviderInfo

type alias AddUserForm =
{ user : User
{ user : NewUser
, password : String
, isPreHashed : Bool
}
Expand All @@ -25,18 +27,39 @@ type alias Role =

type alias User =
{ login : String
, status : UserStatus
, authz : List String
, roles : List String
, rolesCoverage : List String
, customRights : List String
, providers : List String
, providersInfo : ProvidersInfo
}

type UserStatus =
Active
| Disabled
| Deleted

-- Payload to create a new user or update an existing one
type alias NewUser =
{ login : String
, authz : List String
, roles : List String
}

type alias ProviderProperties =
{ roleListOverride : RoleListOverride
, hasModifiablePassword : Bool
}

type alias ProviderInfo =
{ provider : String
, authz : List String
, roles : List String
, customRights : List String
}

-- does the configured list of provider allows to change the list of user roles, and how (extend only, or override?)
-- Note: until rudder core is updated to give us the info, we can only guess based on provider list
type RoleListOverride
Expand All @@ -51,17 +74,61 @@ userProviders: List String -> List String
userProviders providers =
List.filter (\p -> p /= "rootAdmin") providers

takeFirstExtProvider: List String -> Maybe String
takeFirstExtProvider providers =
List.head (List.filter (\p -> p /= "file" && p /= "rootAdmin") providers)
filterExternalProviders: List String -> List String
filterExternalProviders providers =
List.filter (\p -> p /= "file" && p /= "rootAdmin") providers

filterUserProviderByRoleListOverride: RoleListOverride -> Model -> User -> List ProviderInfo
filterUserProviderByRoleListOverride value model user =
user.providers
|> filterExternalProviders
|> List.filter (\p ->
Dict.get p model.providersProperties
|> Maybe.map (\pp -> pp.roleListOverride == value)
|> Maybe.withDefault False
)
|> List.filterMap (\p ->
Dict.get p user.providersInfo
)

getFileProviderInfo: User -> Maybe ProviderInfo
getFileProviderInfo user =
Dict.get "file" user.providersInfo

newUserToUser : NewUser -> User
newUserToUser {login, authz, roles} = {login = login, authz = authz, roles = roles, rolesCoverage = [], customRights = []}
takeFirstExtProvider: List String -> Maybe String
takeFirstExtProvider = List.head << filterExternalProviders

takeFirstOverrideProviderInfo : Model -> User -> Maybe ProviderInfo
takeFirstOverrideProviderInfo model user =
filterUserProviderByRoleListOverride Override model user
|> List.head

takeFirstExtendProviderInfo : Model -> User -> Maybe ProviderInfo
takeFirstExtendProviderInfo model user =
filterUserProviderByRoleListOverride Extend model user
|> List.head

providerHasModifiablePassword : Model -> Provider -> Bool
providerHasModifiablePassword model provider =
provider == "file" || (
Dict.get provider model.providersProperties
|> Maybe.map .hasModifiablePassword
|> Maybe.withDefault False
)

providerCanEditRoles : Model -> Provider -> Bool
providerCanEditRoles model provider =
provider == "file" || (
Dict.get provider model.providersProperties
|> Maybe.map (\p -> p.roleListOverride /= Override)
|> Maybe.withDefault False
)

type alias UsersConf =
{ digest : String
, roleListOverride: RoleListOverride
, authenticationBackends: List String
, providersProperties : Dict String ProviderProperties
, users : List User
}

Expand Down Expand Up @@ -89,6 +156,7 @@ type alias Model =
, isValidInput : StateInput
, rolesToAddOnSave : List String
, providers : List String
, providersProperties: Dict String ProviderProperties
, userForcePasswdInput : Bool
, openDeleteModal : Bool
}
Expand All @@ -101,15 +169,18 @@ type Msg
| AddUser (Result Error String)
| DeleteUser (Result Error String)
| UpdateUser (Result Error String)
| UpdateUserStatus (Result Error Username)
| CallApi (Model -> Cmd Msg)
| ActivePanelSettings User
| ActivePanelAddUser
| DeactivatePanel
| Password String
| Login String
| AddRole String
| RemoveRole User String
| SubmitUpdatedInfos User
| RemoveRole User Provider String
| ActivateUser Username
| DisableUser Username
| SubmitUpdatedInfos NewUser
| SubmitNewUser NewUser
| PreHashedPasswd Bool
| AddPasswdAnyway
Expand Down
2 changes: 1 addition & 1 deletion user-management/src/main/elm/sources/Init.elm
Expand Up @@ -15,7 +15,7 @@ subscriptions model =
init : { contextPath : String } -> ( Model, Cmd Msg )
init flags =
let
initModel = Model flags.contextPath "" (fromList []) (fromList []) [] None Toasty.initialState Closed "" "" True ValidInputs [] [] False False
initModel = Model flags.contextPath "" (fromList []) (fromList []) [] None Toasty.initialState Closed "" "" True ValidInputs [] [] Dict.empty False False
in
( initModel
, getUsersConf initModel
Expand Down
44 changes: 43 additions & 1 deletion user-management/src/main/elm/sources/JsonDecoder.elm
@@ -1,8 +1,11 @@
module JsonDecoder exposing (..)

import DataTypes exposing (Role, RoleConf, RoleListOverride(..), User, UsersConf)
import DataTypes exposing (Role, RoleConf, RoleListOverride(..), User, UserStatus(..), UsersConf)
import Json.Decode as D exposing (Decoder)
import Json.Decode.Pipeline exposing (required)
import DataTypes exposing (ProviderInfo)
import DataTypes exposing (ProvidersInfo)
import DataTypes exposing (ProviderProperties)

decodeApiReloadResult : Decoder String
decodeApiReloadResult =
Expand All @@ -24,8 +27,15 @@ decodeCurrentUsersConf =
|> required "digest" D.string
|> required "roleListOverride" decodeRoleListOverride
|> required "authenticationBackends" (D.list <| D.string)
|> required "providerProperties" (D.dict decodeProviderProperties)
|> required "users" (D.list <| decodeUser)

decodeProviderProperties : Decoder ProviderProperties
decodeProviderProperties =
D.succeed ProviderProperties
|> required "roleListOverride" decodeRoleListOverride
|> required "hasModifiablePassword" D.bool

decodeRoleListOverride : Decoder RoleListOverride
decodeRoleListOverride =
D.string |> D.andThen
Expand All @@ -36,15 +46,40 @@ decodeRoleListOverride =
_ -> D.succeed None
)

decodeProviderInfo : Decoder ProviderInfo
decodeProviderInfo =
D.succeed ProviderInfo
|> required "provider" D.string
|> required "authz" (D.list <| D.string)
|> required "roles" (D.list <| D.string)
|> required "customRights" (D.list <| D.string)

-- Decode dict of providers info, the key is the provider
decodeProvidersInfo : Decoder ProvidersInfo
decodeProvidersInfo =
D.dict decodeProviderInfo

decodeUser : Decoder User
decodeUser =
D.succeed User
|> required "login" D.string
|> required "status" decodeUserStatus
|> required "authz" (D.list <| D.string)
|> required "permissions" (D.list <| D.string)
|> required "rolesCoverage" (D.list <| D.string)
|> required "customRights" (D.list <| D.string)
|> required "providers" (D.list <| D.string)
|> required "providersInfo" decodeProvidersInfo

decodeUserStatus : Decoder UserStatus
decodeUserStatus =
D.string |> D.andThen
(\str ->
case str of
"active" -> D.succeed Active
"disabled" -> D.succeed Disabled
_ -> D.succeed Deleted
)

decodeApiAddUserResult : Decoder String
decodeApiAddUserResult =
Expand All @@ -70,6 +105,13 @@ decodeDeletedUser : Decoder String
decodeDeletedUser =
D.at [ "deletedUser" ] (D.at [ "username" ] D.string)

decodeApiStatusResult : Decoder String
decodeApiStatusResult =
D.at [ "data" ] decodeStatus

decodeStatus : Decoder String
decodeStatus =
D.at [ "status" ] D.string
decodeRole : Decoder Role
decodeRole =
D.succeed Role
Expand Down
32 changes: 23 additions & 9 deletions user-management/src/main/elm/sources/UserManagement.elm
@@ -1,8 +1,8 @@
module UserManagement exposing (processApiError, update)

import ApiCalls exposing (addUser, getRoleConf, getUsersConf, postReloadConf, updateUser)
import ApiCalls exposing (activateUser, addUser, disableUser, getRoleConf, getUsersConf, postReloadConf, updateUser)
import Browser
import DataTypes exposing (Model, Msg(..), PanelMode(..), StateInput(..), newUserToUser, userProviders)
import DataTypes exposing (Model, Msg(..), PanelMode(..), StateInput(..), userProviders)
import Dict exposing (fromList)
import Http exposing (..)
import Init exposing (createErrorNotification, createSuccessNotification, defaultConfig, init, subscriptions)
Expand Down Expand Up @@ -44,7 +44,7 @@ update msg model =
_ ->
Closed
newModel =
{ model | roleListOverride = u.roleListOverride, users = users, panelMode = newPanelMode, digest = u.digest, providers = (userProviders u.authenticationBackends)}
{ model | roleListOverride = u.roleListOverride, users = users, panelMode = newPanelMode, digest = u.digest, providers = (userProviders u.authenticationBackends), providersProperties = u.providersProperties}

in
( newModel, getRoleConf model )
Expand Down Expand Up @@ -123,12 +123,21 @@ update msg model =
Err err ->
processApiError err model

UpdateUserStatus result ->
case result of
Ok username ->
(model, getUsersConf model)
|> createSuccessNotification (username ++ " have been modified")

Err err ->
processApiError err model

AddRole r ->
({model | rolesToAddOnSave = r :: model.rolesToAddOnSave}, Cmd.none)
RemoveRole user r ->
RemoveRole user provider r ->
let
-- remove role, and also authz that are associated to the role but not associated with any other remaining role
newRoles = user.roles |> List.filter (\x -> r /= x)
newRoles = Dict.get provider user.providersInfo |> Maybe.map .roles |> Maybe.withDefault [] |> List.filter (\x -> r /= x)
newAuthz =
-- keep authz if it is found in any authz of newRoles
-- keep authz if it's in custom authz of the user
Expand All @@ -142,9 +151,10 @@ update msg model =
newRoles
|> List.any (\y -> List.member y allAuthz)
) user.authz
newUser = {user | roles = newRoles, authz = newAuthz}
newUser = {login = user.login, authz = newAuthz, roles = newRoles}
newPanelMode = EditMode {user | authz = newAuthz, roles = newRoles}
in
({model | panelMode = EditMode newUser}, updateUser model user.login (DataTypes.AddUserForm newUser "" model.isHashedPasswd))
({model | panelMode = newPanelMode}, updateUser model user.login (DataTypes.AddUserForm newUser "" model.isHashedPasswd))
Notification subMsg ->
Toasty.update defaultConfig Notification subMsg model

Expand All @@ -153,7 +163,7 @@ update msg model =
Login newLogin ->
({model | isValidInput = ValidInputs, login = newLogin}, Cmd.none)
SubmitUpdatedInfos u ->
({model | rolesToAddOnSave = [], password = "", userForcePasswdInput = False, login = ""}, updateUser model u.login (DataTypes.AddUserForm { u | login = u.login} model.password model.isHashedPasswd))
({model | rolesToAddOnSave = [], password = "", userForcePasswdInput = False, login = ""}, updateUser model u.login (DataTypes.AddUserForm u model.password model.isHashedPasswd))
SubmitNewUser u ->
if(isEmpty u.login) then
({model | isValidInput = InvalidUsername}, Cmd.none)
Expand All @@ -167,8 +177,12 @@ update msg model =
, isValidInput = ValidInputs
, rolesToAddOnSave = []
}
, addUser model (DataTypes.AddUserForm (newUserToUser u) model.password model.isHashedPasswd)
, addUser model (DataTypes.AddUserForm u model.password model.isHashedPasswd)
)
ActivateUser username ->
(model, activateUser model username)
DisableUser username ->
(model, disableUser model username)

PreHashedPasswd bool ->
({model | password = "",isHashedPasswd = bool}, Cmd.none)
Expand Down

0 comments on commit 8f6ae6d

Please sign in to comment.