xquery version "1.0";
(:
    ART-DECOR® STANDARD COPYRIGHT AND LICENSE NOTE
    Copyright © ART-DECOR Expert Group and ART-DECOR Open Tools GmbH
    see https://docs.art-decor.org/copyright and https://docs.art-decor.org/licenses

    This file is part of the ART-DECOR® tools suite.
:)
import module namespace get         = "http://art-decor.org/ns/art-decor-settings" at "art-decor-settings.xqm";
import module namespace aduser      = "http://art-decor.org/ns/art-decor-users" at "../api/api-user-settings.xqm";
import module namespace adserver    = "http://art-decor.org/ns/art-decor-server" at "../api/api-server-settings.xqm";

declare namespace sm           = "http://exist-db.org/xquery/securitymanager";
declare variable $PROTECTEDGROUPS   := ('dba', 'decor', 'decor-admin', 'terminology', 'terminology-admin', 'ada-user', 'issues', 'guest', 'editor', 'debug', 'tools', 'xis', 'monex', 'eXide', 'packageservice');

let $propDescription        := xs:anyURI('http://exist-db.org/security/description')
let $userInfo               := if (request:exists()) then (request:get-data()/user) else (
    <user name="testuser" active="true" newpwd="" newpwd-confirm="">
        <!-- account-info -->
        <groups><group>guest</group></groups>
        <primarygroup>guest</primarygroup>
        <description>test</description>
        
        <!-- user-info -->
        <defaultLanguage>{$get:strArtLanguage}</defaultLanguage>
        <displayName>Test user</displayName>
        <email></email>
        <organization>Test organization</organization>
    </user>
)

let $userName               := $userInfo/@name[string-length() gt 0]
let $userPwd                := $userInfo[string(@newpwd) = string(@newpwd-confirm)]/@newpwd[not(. = '')]

let $userIsActive           := if ($userInfo/@active castable as xs:boolean) then (xs:boolean($userInfo/@active)) else true()
let $userGroups             := $userInfo/groups/group
(: Note the primary group you set should exist. If it does not exist, the primary group will default
   to the first group that the user happens to be in based on the request :)
let $userPrimaryGroup       := $userInfo/primarygroup[string-length() gt 0]
let $userDescription        := if ($userInfo/description[string-length() gt 0]) then $userInfo/description else ('')

(: user-info Initially set by the admin that creates the account, but also user editable from there on :)
let $userLanguage           := $userInfo/defaultLanguage[string-length() gt 0]
let $userDisplayName        := $userInfo/displayName[string-length() gt 0]
let $userEmail              := $userInfo/email[string-length() gt 0]
let $userOrganization       := $userInfo/organization[string-length() gt 0]

(: Save user details for all users except SYSTEM :)
let $userSaved              :=
    if (empty($userName)) then
        error(xs:QName('aduser:save-user'), 'You SHALL provide a user name')
    else
    if ($userName = 'SYSTEM') then
        error(xs:QName('aduser:save-user'), 'You SHALL NOT edit user SYSTEM')
    else
    if ($userName = $PROTECTEDGROUPS) then
        error(xs:QName('aduser:save-user'), concat('You SHALL NOT create or update a user that matches a protected group. Found: ', $userName, '. Protected groups: ', string-join($PROTECTEDGROUPS, ', ')))
    else
    if (sm:user-exists($userName)) then (
        (:updated user:)
        let $currentGroups        := sm:get-user-groups($userName)
        
        let $actionAddGroups      := 
            for $group in $userGroups[not(.=$currentGroups)]
            return
                sm:add-group-member($group,$userName)
        
        let $actionRemoveGroups   := 
            for $group in $currentGroups[not(.=$userGroups)]
            return
                sm:remove-group-member($group,$userName)
        
        let $actionPrimaryGroup   := if (empty($userPrimaryGroup)) then () else sm:set-user-primary-group($userName,$userPrimaryGroup)
        let $actionDescription    := sm:set-account-metadata($userName, $propDescription, $userDescription)
        
        let $actionActivate       := sm:set-account-enabled($userName,$userIsActive)
        let $actionPwdUpdate      := 
            if (empty($userPwd)) then () else (
                sm:passwd($userName,$userPwd)
                ,
                (: Update server-info :)
                if ($userName=$adserver:arrSystemLevelUsers) then 
                    adserver:setPassword($userName, $userPwd)
                else ()
            )
        
        (: hack - we keep loosing the index on the user info file, so reindex before starting updates :)
        let $reindex              := xmldb:reindex($get:strUserInfo)
        (: Update user-info :)
        let $updatedExtraInfo     := aduser:setUserInfo($userName, $userLanguage, $userDisplayName, $userEmail, $userOrganization)
        
        return true()
    ) 
    else (
        if (empty($userPwd)) then
            error(xs:QName('aduser:save-user'), 'You SHALL provide a password and a matching confirmation password for a new user')
        else (
            (:new user:)
            let $actionDeleteUserGroup  :=
                (:  This may happen if we have ever created this user before, and deleted him through the eXist-db user manager. 
                    The group then lives on and sm:create-account() will fail on that group
                    We may delete this group if it is empty and not one of the protected groups (dba, decor, decor-admin, terminology, ada-user, issues, guest, editor, debug, tools, xis)
                    The latter is already checked earlier in this routine
                :)
                if (sm:group-exists($userName)) then
                    if (empty(sm:get-group-members($userName))) then
                        sm:remove-group($userName)
                    else (
                        (: cannot be helped. this group already has members. we cannot delete a group with members in it :)
                    )
                else ()
            let $actionCreate           := sm:create-account($userName, $userPwd, $userGroups)
            let $actionPrimaryGroup     := if (empty($userPrimaryGroup)) then () else sm:set-user-primary-group($userName, $userPrimaryGroup)
            let $actionDescription      := if (empty($userDescription)) then () else sm:set-account-metadata($userName, $propDescription, $userDescription)
            
            let $actionActivate         := sm:set-account-enabled($userName, $userIsActive)
            
            (: hack - we keep loosing the index on the user info file, so reindex before starting updates :)
            let $reindex                := xmldb:reindex($get:strUserInfo)
            (: Cannot add user-info before the user exists, so make this last step :)
            let $updatedExtraInfo       := aduser:setUserInfo($userName, $userLanguage, $userDisplayName, $userEmail, $userOrganization)
            
            (: Update server-info :)
            let $updateServerSettings:=
                if ($userName=$adserver:arrSystemLevelUsers) then 
                    adserver:setPassword($userName, $userPwd)
                else ()
            
            return true()
        ) 
    )

(:2015-1014 AH. Hack we need the groups in user-info.xml to match the eXist-db groups so we can offer this info to non-dba users, 
                e.g. decor-admin/terminology users that need to add authors that are in the right groups:)
let $updateGroups           :=
    if (sm:is-dba(get:strCurrentUserName())) then (
        for $user in sm:list-users()[not(.='SYSTEM')]
        return 
            aduser:setUserGroups($user)
    ) else ()

return
    <data-safe>{$userSaved}</data-safe>