| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436 | 
							- // Copyright (C) 2019 Nicola Murino
 
- //
 
- // This program is free software: you can redistribute it and/or modify
 
- // it under the terms of the GNU Affero General Public License as published
 
- // by the Free Software Foundation, version 3.
 
- //
 
- // This program is distributed in the hope that it will be useful,
 
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
 
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 
- // GNU Affero General Public License for more details.
 
- //
 
- // You should have received a copy of the GNU Affero General Public License
 
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
- package httpd
 
- import (
 
- 	"context"
 
- 	"encoding/json"
 
- 	"errors"
 
- 	"fmt"
 
- 	"html/template"
 
- 	"io"
 
- 	"net/http"
 
- 	"net/url"
 
- 	"os"
 
- 	"path/filepath"
 
- 	"slices"
 
- 	"sort"
 
- 	"strconv"
 
- 	"strings"
 
- 	"time"
 
- 	"github.com/go-chi/render"
 
- 	"github.com/rs/xid"
 
- 	"github.com/sftpgo/sdk"
 
- 	sdkkms "github.com/sftpgo/sdk/kms"
 
- 	"github.com/drakkan/sftpgo/v2/internal/acme"
 
- 	"github.com/drakkan/sftpgo/v2/internal/common"
 
- 	"github.com/drakkan/sftpgo/v2/internal/dataprovider"
 
- 	"github.com/drakkan/sftpgo/v2/internal/kms"
 
- 	"github.com/drakkan/sftpgo/v2/internal/logger"
 
- 	"github.com/drakkan/sftpgo/v2/internal/mfa"
 
- 	"github.com/drakkan/sftpgo/v2/internal/plugin"
 
- 	"github.com/drakkan/sftpgo/v2/internal/smtp"
 
- 	"github.com/drakkan/sftpgo/v2/internal/util"
 
- 	"github.com/drakkan/sftpgo/v2/internal/vfs"
 
- )
 
- type userPageMode int
 
- const (
 
- 	userPageModeAdd userPageMode = iota + 1
 
- 	userPageModeUpdate
 
- 	userPageModeTemplate
 
- )
 
- type folderPageMode int
 
- const (
 
- 	folderPageModeAdd folderPageMode = iota + 1
 
- 	folderPageModeUpdate
 
- 	folderPageModeTemplate
 
- )
 
- type genericPageMode int
 
- const (
 
- 	genericPageModeAdd genericPageMode = iota + 1
 
- 	genericPageModeUpdate
 
- )
 
- const (
 
- 	templateAdminDir         = "webadmin"
 
- 	templateBase             = "base.html"
 
- 	templateFsConfig         = "fsconfig.html"
 
- 	templateSharedComponents = "sharedcomponents.html"
 
- 	templateUsers            = "users.html"
 
- 	templateUser             = "user.html"
 
- 	templateAdmins           = "admins.html"
 
- 	templateAdmin            = "admin.html"
 
- 	templateConnections      = "connections.html"
 
- 	templateGroups           = "groups.html"
 
- 	templateGroup            = "group.html"
 
- 	templateFolders          = "folders.html"
 
- 	templateFolder           = "folder.html"
 
- 	templateEventRules       = "eventrules.html"
 
- 	templateEventRule        = "eventrule.html"
 
- 	templateEventActions     = "eventactions.html"
 
- 	templateEventAction      = "eventaction.html"
 
- 	templateRoles            = "roles.html"
 
- 	templateRole             = "role.html"
 
- 	templateEvents           = "events.html"
 
- 	templateStatus           = "status.html"
 
- 	templateDefender         = "defender.html"
 
- 	templateIPLists          = "iplists.html"
 
- 	templateIPList           = "iplist.html"
 
- 	templateConfigs          = "configs.html"
 
- 	templateProfile          = "profile.html"
 
- 	templateMaintenance      = "maintenance.html"
 
- 	templateMFA              = "mfa.html"
 
- 	templateSetup            = "adminsetup.html"
 
- 	defaultQueryLimit        = 1000
 
- 	inversePatternType       = "inverse"
 
- )
 
- var (
 
- 	adminTemplates = make(map[string]*template.Template)
 
- )
 
- type basePage struct {
 
- 	commonBasePage
 
- 	Title               string
 
- 	CurrentURL          string
 
- 	UsersURL            string
 
- 	UserURL             string
 
- 	UserTemplateURL     string
 
- 	AdminsURL           string
 
- 	AdminURL            string
 
- 	QuotaScanURL        string
 
- 	ConnectionsURL      string
 
- 	GroupsURL           string
 
- 	GroupURL            string
 
- 	FoldersURL          string
 
- 	FolderURL           string
 
- 	FolderTemplateURL   string
 
- 	DefenderURL         string
 
- 	IPListsURL          string
 
- 	IPListURL           string
 
- 	EventsURL           string
 
- 	ConfigsURL          string
 
- 	LogoutURL           string
 
- 	LoginURL            string
 
- 	ProfileURL          string
 
- 	ChangePwdURL        string
 
- 	MFAURL              string
 
- 	EventRulesURL       string
 
- 	EventRuleURL        string
 
- 	EventActionsURL     string
 
- 	EventActionURL      string
 
- 	RolesURL            string
 
- 	RoleURL             string
 
- 	FolderQuotaScanURL  string
 
- 	StatusURL           string
 
- 	MaintenanceURL      string
 
- 	CSRFToken           string
 
- 	IsEventManagerPage  bool
 
- 	IsIPManagerPage     bool
 
- 	IsServerManagerPage bool
 
- 	HasDefender         bool
 
- 	HasSearcher         bool
 
- 	HasExternalLogin    bool
 
- 	LoggedUser          *dataprovider.Admin
 
- 	IsLoggedToShare     bool
 
- 	Branding            UIBranding
 
- }
 
- type statusPage struct {
 
- 	basePage
 
- 	Status *ServicesStatus
 
- }
 
- type fsWrapper struct {
 
- 	vfs.Filesystem
 
- 	IsUserPage      bool
 
- 	IsGroupPage     bool
 
- 	IsHidden        bool
 
- 	HasUsersBaseDir bool
 
- 	DirPath         string
 
- }
 
- type userPage struct {
 
- 	basePage
 
- 	User               *dataprovider.User
 
- 	RootPerms          []string
 
- 	Error              *util.I18nError
 
- 	ValidPerms         []string
 
- 	ValidLoginMethods  []string
 
- 	ValidProtocols     []string
 
- 	TwoFactorProtocols []string
 
- 	WebClientOptions   []string
 
- 	RootDirPerms       []string
 
- 	Mode               userPageMode
 
- 	VirtualFolders     []vfs.BaseVirtualFolder
 
- 	Groups             []dataprovider.Group
 
- 	Roles              []dataprovider.Role
 
- 	CanImpersonate     bool
 
- 	FsWrapper          fsWrapper
 
- }
 
- type adminPage struct {
 
- 	basePage
 
- 	Admin  *dataprovider.Admin
 
- 	Groups []dataprovider.Group
 
- 	Roles  []dataprovider.Role
 
- 	Error  *util.I18nError
 
- 	IsAdd  bool
 
- }
 
- type profilePage struct {
 
- 	basePage
 
- 	Error           *util.I18nError
 
- 	AllowAPIKeyAuth bool
 
- 	Email           string
 
- 	Description     string
 
- }
 
- type changePasswordPage struct {
 
- 	basePage
 
- 	Error *util.I18nError
 
- }
 
- type mfaPage struct {
 
- 	basePage
 
- 	TOTPConfigs      []string
 
- 	TOTPConfig       dataprovider.AdminTOTPConfig
 
- 	GenerateTOTPURL  string
 
- 	ValidateTOTPURL  string
 
- 	SaveTOTPURL      string
 
- 	RecCodesURL      string
 
- 	RequireTwoFactor bool
 
- }
 
- type maintenancePage struct {
 
- 	basePage
 
- 	BackupPath  string
 
- 	RestorePath string
 
- 	Error       *util.I18nError
 
- }
 
- type defenderHostsPage struct {
 
- 	basePage
 
- 	DefenderHostsURL string
 
- }
 
- type ipListsPage struct {
 
- 	basePage
 
- 	IPListsSearchURL      string
 
- 	RateLimitersStatus    bool
 
- 	RateLimitersProtocols string
 
- 	IsAllowListEnabled    bool
 
- }
 
- type ipListPage struct {
 
- 	basePage
 
- 	Entry *dataprovider.IPListEntry
 
- 	Error *util.I18nError
 
- 	Mode  genericPageMode
 
- }
 
- type setupPage struct {
 
- 	commonBasePage
 
- 	CurrentURL           string
 
- 	Error                *util.I18nError
 
- 	CSRFToken            string
 
- 	Username             string
 
- 	HasInstallationCode  bool
 
- 	InstallationCodeHint string
 
- 	HideSupportLink      bool
 
- 	Title                string
 
- 	Branding             UIBranding
 
- }
 
- type folderPage struct {
 
- 	basePage
 
- 	Folder    vfs.BaseVirtualFolder
 
- 	Error     *util.I18nError
 
- 	Mode      folderPageMode
 
- 	FsWrapper fsWrapper
 
- }
 
- type groupPage struct {
 
- 	basePage
 
- 	Group              *dataprovider.Group
 
- 	Error              *util.I18nError
 
- 	Mode               genericPageMode
 
- 	ValidPerms         []string
 
- 	ValidLoginMethods  []string
 
- 	ValidProtocols     []string
 
- 	TwoFactorProtocols []string
 
- 	WebClientOptions   []string
 
- 	VirtualFolders     []vfs.BaseVirtualFolder
 
- 	FsWrapper          fsWrapper
 
- }
 
- type rolePage struct {
 
- 	basePage
 
- 	Role  *dataprovider.Role
 
- 	Error *util.I18nError
 
- 	Mode  genericPageMode
 
- }
 
- type eventActionPage struct {
 
- 	basePage
 
- 	Action         dataprovider.BaseEventAction
 
- 	ActionTypes    []dataprovider.EnumMapping
 
- 	FsActions      []dataprovider.EnumMapping
 
- 	HTTPMethods    []string
 
- 	RedactedSecret string
 
- 	Error          *util.I18nError
 
- 	Mode           genericPageMode
 
- }
 
- type eventRulePage struct {
 
- 	basePage
 
- 	Rule            dataprovider.EventRule
 
- 	TriggerTypes    []dataprovider.EnumMapping
 
- 	Actions         []dataprovider.BaseEventAction
 
- 	FsEvents        []string
 
- 	Protocols       []string
 
- 	ProviderEvents  []string
 
- 	ProviderObjects []string
 
- 	Error           *util.I18nError
 
- 	Mode            genericPageMode
 
- 	IsShared        bool
 
- }
 
- type eventsPage struct {
 
- 	basePage
 
- 	FsEventsSearchURL       string
 
- 	ProviderEventsSearchURL string
 
- 	LogEventsSearchURL      string
 
- }
 
- type configsPage struct {
 
- 	basePage
 
- 	Configs           dataprovider.Configs
 
- 	ConfigSection     int
 
- 	RedactedSecret    string
 
- 	OAuth2TokenURL    string
 
- 	OAuth2RedirectURL string
 
- 	WebClientBranding UIBranding
 
- 	Error             *util.I18nError
 
- }
 
- type messagePage struct {
 
- 	basePage
 
- 	Error   *util.I18nError
 
- 	Success string
 
- 	Text    string
 
- }
 
- type userTemplateFields struct {
 
- 	Username   string
 
- 	Password   string
 
- 	PublicKeys []string
 
- }
 
- func loadAdminTemplates(templatesPath string) {
 
- 	usersPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateUsers),
 
- 	}
 
- 	userPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateUser),
 
- 	}
 
- 	adminsPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateAdmins),
 
- 	}
 
- 	adminPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateAdmin),
 
- 	}
 
- 	profilePaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateProfile),
 
- 	}
 
- 	changePwdPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateChangePwd),
 
- 	}
 
- 	connectionsPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateConnections),
 
- 	}
 
- 	messagePaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateMessage),
 
- 	}
 
- 	foldersPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateFolders),
 
- 	}
 
- 	folderPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateFolder),
 
- 	}
 
- 	groupsPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateGroups),
 
- 	}
 
- 	groupPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateFsConfig),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateGroup),
 
- 	}
 
- 	eventRulesPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateEventRules),
 
- 	}
 
- 	eventRulePaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateEventRule),
 
- 	}
 
- 	eventActionsPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateEventActions),
 
- 	}
 
- 	eventActionPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateEventAction),
 
- 	}
 
- 	statusPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateStatus),
 
- 	}
 
- 	loginPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonLogin),
 
- 	}
 
- 	maintenancePaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateMaintenance),
 
- 	}
 
- 	defenderPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateDefender),
 
- 	}
 
- 	ipListsPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateIPLists),
 
- 	}
 
- 	ipListPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateIPList),
 
- 	}
 
- 	mfaPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateMFA),
 
- 	}
 
- 	twoFactorPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateTwoFactor),
 
- 	}
 
- 	twoFactorRecoveryPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateTwoFactorRecovery),
 
- 	}
 
- 	setupPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateSetup),
 
- 	}
 
- 	forgotPwdPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateForgotPassword),
 
- 	}
 
- 	resetPwdPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
 
- 		filepath.Join(templatesPath, templateCommonDir, templateResetPassword),
 
- 	}
 
- 	rolesPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateRoles),
 
- 	}
 
- 	rolePaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateRole),
 
- 	}
 
- 	eventsPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateEvents),
 
- 	}
 
- 	configsPaths := []string{
 
- 		filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateBase),
 
- 		filepath.Join(templatesPath, templateAdminDir, templateConfigs),
 
- 	}
 
- 	fsBaseTpl := template.New("fsBaseTemplate").Funcs(template.FuncMap{
 
- 		"HumanizeBytes": util.ByteCountSI,
 
- 		"IsFsDisabled":  vfs.IsFsDisabled,
 
- 	})
 
- 	usersTmpl := util.LoadTemplate(nil, usersPaths...)
 
- 	userTmpl := util.LoadTemplate(fsBaseTpl, userPaths...)
 
- 	adminsTmpl := util.LoadTemplate(nil, adminsPaths...)
 
- 	adminTmpl := util.LoadTemplate(nil, adminPaths...)
 
- 	connectionsTmpl := util.LoadTemplate(nil, connectionsPaths...)
 
- 	messageTmpl := util.LoadTemplate(nil, messagePaths...)
 
- 	groupsTmpl := util.LoadTemplate(nil, groupsPaths...)
 
- 	groupTmpl := util.LoadTemplate(fsBaseTpl, groupPaths...)
 
- 	foldersTmpl := util.LoadTemplate(nil, foldersPaths...)
 
- 	folderTmpl := util.LoadTemplate(fsBaseTpl, folderPaths...)
 
- 	eventRulesTmpl := util.LoadTemplate(nil, eventRulesPaths...)
 
- 	eventRuleTmpl := util.LoadTemplate(fsBaseTpl, eventRulePaths...)
 
- 	eventActionsTmpl := util.LoadTemplate(nil, eventActionsPaths...)
 
- 	eventActionTmpl := util.LoadTemplate(nil, eventActionPaths...)
 
- 	statusTmpl := util.LoadTemplate(nil, statusPaths...)
 
- 	loginTmpl := util.LoadTemplate(nil, loginPaths...)
 
- 	profileTmpl := util.LoadTemplate(nil, profilePaths...)
 
- 	changePwdTmpl := util.LoadTemplate(nil, changePwdPaths...)
 
- 	maintenanceTmpl := util.LoadTemplate(nil, maintenancePaths...)
 
- 	defenderTmpl := util.LoadTemplate(nil, defenderPaths...)
 
- 	ipListsTmpl := util.LoadTemplate(nil, ipListsPaths...)
 
- 	ipListTmpl := util.LoadTemplate(nil, ipListPaths...)
 
- 	mfaTmpl := util.LoadTemplate(nil, mfaPaths...)
 
- 	twoFactorTmpl := util.LoadTemplate(nil, twoFactorPaths...)
 
- 	twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPaths...)
 
- 	setupTmpl := util.LoadTemplate(nil, setupPaths...)
 
- 	forgotPwdTmpl := util.LoadTemplate(nil, forgotPwdPaths...)
 
- 	resetPwdTmpl := util.LoadTemplate(nil, resetPwdPaths...)
 
- 	rolesTmpl := util.LoadTemplate(nil, rolesPaths...)
 
- 	roleTmpl := util.LoadTemplate(nil, rolePaths...)
 
- 	eventsTmpl := util.LoadTemplate(nil, eventsPaths...)
 
- 	configsTmpl := util.LoadTemplate(nil, configsPaths...)
 
- 	adminTemplates[templateUsers] = usersTmpl
 
- 	adminTemplates[templateUser] = userTmpl
 
- 	adminTemplates[templateAdmins] = adminsTmpl
 
- 	adminTemplates[templateAdmin] = adminTmpl
 
- 	adminTemplates[templateConnections] = connectionsTmpl
 
- 	adminTemplates[templateMessage] = messageTmpl
 
- 	adminTemplates[templateGroups] = groupsTmpl
 
- 	adminTemplates[templateGroup] = groupTmpl
 
- 	adminTemplates[templateFolders] = foldersTmpl
 
- 	adminTemplates[templateFolder] = folderTmpl
 
- 	adminTemplates[templateEventRules] = eventRulesTmpl
 
- 	adminTemplates[templateEventRule] = eventRuleTmpl
 
- 	adminTemplates[templateEventActions] = eventActionsTmpl
 
- 	adminTemplates[templateEventAction] = eventActionTmpl
 
- 	adminTemplates[templateStatus] = statusTmpl
 
- 	adminTemplates[templateCommonLogin] = loginTmpl
 
- 	adminTemplates[templateProfile] = profileTmpl
 
- 	adminTemplates[templateChangePwd] = changePwdTmpl
 
- 	adminTemplates[templateMaintenance] = maintenanceTmpl
 
- 	adminTemplates[templateDefender] = defenderTmpl
 
- 	adminTemplates[templateIPLists] = ipListsTmpl
 
- 	adminTemplates[templateIPList] = ipListTmpl
 
- 	adminTemplates[templateMFA] = mfaTmpl
 
- 	adminTemplates[templateTwoFactor] = twoFactorTmpl
 
- 	adminTemplates[templateTwoFactorRecovery] = twoFactorRecoveryTmpl
 
- 	adminTemplates[templateSetup] = setupTmpl
 
- 	adminTemplates[templateForgotPassword] = forgotPwdTmpl
 
- 	adminTemplates[templateResetPassword] = resetPwdTmpl
 
- 	adminTemplates[templateRoles] = rolesTmpl
 
- 	adminTemplates[templateRole] = roleTmpl
 
- 	adminTemplates[templateEvents] = eventsTmpl
 
- 	adminTemplates[templateConfigs] = configsTmpl
 
- }
 
- func isEventManagerResource(currentURL string) bool {
 
- 	if currentURL == webAdminEventRulesPath {
 
- 		return true
 
- 	}
 
- 	if currentURL == webAdminEventActionsPath {
 
- 		return true
 
- 	}
 
- 	if currentURL == webAdminEventRulePath || strings.HasPrefix(currentURL, webAdminEventRulePath+"/") {
 
- 		return true
 
- 	}
 
- 	if currentURL == webAdminEventActionPath || strings.HasPrefix(currentURL, webAdminEventActionPath+"/") {
 
- 		return true
 
- 	}
 
- 	return false
 
- }
 
- func isIPListsResource(currentURL string) bool {
 
- 	if currentURL == webDefenderPath {
 
- 		return true
 
- 	}
 
- 	if currentURL == webIPListsPath {
 
- 		return true
 
- 	}
 
- 	if strings.HasPrefix(currentURL, webIPListPath+"/") {
 
- 		return true
 
- 	}
 
- 	return false
 
- }
 
- func isServerManagerResource(currentURL string) bool {
 
- 	return currentURL == webEventsPath || currentURL == webStatusPath || currentURL == webMaintenancePath ||
 
- 		currentURL == webConfigsPath
 
- }
 
- func (s *httpdServer) getBasePageData(title, currentURL string, w http.ResponseWriter, r *http.Request) basePage {
 
- 	var csrfToken string
 
- 	if currentURL != "" {
 
- 		csrfToken = createCSRFToken(w, r, s.csrfTokenAuth, "", webBaseAdminPath)
 
- 	}
 
- 	return basePage{
 
- 		commonBasePage:      getCommonBasePage(r),
 
- 		Title:               title,
 
- 		CurrentURL:          currentURL,
 
- 		UsersURL:            webUsersPath,
 
- 		UserURL:             webUserPath,
 
- 		UserTemplateURL:     webTemplateUser,
 
- 		AdminsURL:           webAdminsPath,
 
- 		AdminURL:            webAdminPath,
 
- 		GroupsURL:           webGroupsPath,
 
- 		GroupURL:            webGroupPath,
 
- 		FoldersURL:          webFoldersPath,
 
- 		FolderURL:           webFolderPath,
 
- 		FolderTemplateURL:   webTemplateFolder,
 
- 		DefenderURL:         webDefenderPath,
 
- 		IPListsURL:          webIPListsPath,
 
- 		IPListURL:           webIPListPath,
 
- 		EventsURL:           webEventsPath,
 
- 		ConfigsURL:          webConfigsPath,
 
- 		LogoutURL:           webLogoutPath,
 
- 		LoginURL:            webAdminLoginPath,
 
- 		ProfileURL:          webAdminProfilePath,
 
- 		ChangePwdURL:        webChangeAdminPwdPath,
 
- 		MFAURL:              webAdminMFAPath,
 
- 		EventRulesURL:       webAdminEventRulesPath,
 
- 		EventRuleURL:        webAdminEventRulePath,
 
- 		EventActionsURL:     webAdminEventActionsPath,
 
- 		EventActionURL:      webAdminEventActionPath,
 
- 		RolesURL:            webAdminRolesPath,
 
- 		RoleURL:             webAdminRolePath,
 
- 		QuotaScanURL:        webQuotaScanPath,
 
- 		ConnectionsURL:      webConnectionsPath,
 
- 		StatusURL:           webStatusPath,
 
- 		FolderQuotaScanURL:  webScanVFolderPath,
 
- 		MaintenanceURL:      webMaintenancePath,
 
- 		LoggedUser:          getAdminFromToken(r),
 
- 		IsEventManagerPage:  isEventManagerResource(currentURL),
 
- 		IsIPManagerPage:     isIPListsResource(currentURL),
 
- 		IsServerManagerPage: isServerManagerResource(currentURL),
 
- 		HasDefender:         common.Config.DefenderConfig.Enabled,
 
- 		HasSearcher:         plugin.Handler.HasSearcher(),
 
- 		HasExternalLogin:    isLoggedInWithOIDC(r),
 
- 		CSRFToken:           csrfToken,
 
- 		Branding:            s.binding.webAdminBranding(),
 
- 	}
 
- }
 
- func renderAdminTemplate(w http.ResponseWriter, tmplName string, data any) {
 
- 	err := adminTemplates[tmplName].ExecuteTemplate(w, tmplName, data)
 
- 	if err != nil {
 
- 		http.Error(w, err.Error(), http.StatusInternalServerError)
 
- 	}
 
- }
 
- func (s *httpdServer) renderMessagePageWithString(w http.ResponseWriter, r *http.Request, title string, statusCode int,
 
- 	err error, message, text string,
 
- ) {
 
- 	data := messagePage{
 
- 		basePage: s.getBasePageData(title, "", w, r),
 
- 		Error:    getI18nError(err),
 
- 		Success:  message,
 
- 		Text:     text,
 
- 	}
 
- 	w.WriteHeader(statusCode)
 
- 	renderAdminTemplate(w, templateMessage, data)
 
- }
 
- func (s *httpdServer) renderMessagePage(w http.ResponseWriter, r *http.Request, title string, statusCode int,
 
- 	err error, message string,
 
- ) {
 
- 	s.renderMessagePageWithString(w, r, title, statusCode, err, message, "")
 
- }
 
- func (s *httpdServer) renderInternalServerErrorPage(w http.ResponseWriter, r *http.Request, err error) {
 
- 	s.renderMessagePage(w, r, util.I18nError500Title, http.StatusInternalServerError,
 
- 		util.NewI18nError(err, util.I18nError500Message), "")
 
- }
 
- func (s *httpdServer) renderBadRequestPage(w http.ResponseWriter, r *http.Request, err error) {
 
- 	s.renderMessagePage(w, r, util.I18nError400Title, http.StatusBadRequest,
 
- 		util.NewI18nError(err, util.I18nError400Message), "")
 
- }
 
- func (s *httpdServer) renderForbiddenPage(w http.ResponseWriter, r *http.Request, err error) {
 
- 	s.renderMessagePage(w, r, util.I18nError403Title, http.StatusForbidden,
 
- 		util.NewI18nError(err, util.I18nError403Message), "")
 
- }
 
- func (s *httpdServer) renderNotFoundPage(w http.ResponseWriter, r *http.Request, err error) {
 
- 	s.renderMessagePage(w, r, util.I18nError404Title, http.StatusNotFound,
 
- 		util.NewI18nError(err, util.I18nError404Message), "")
 
- }
 
- func (s *httpdServer) renderForgotPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError) {
 
- 	data := forgotPwdPage{
 
- 		commonBasePage: getCommonBasePage(r),
 
- 		CurrentURL:     webAdminForgotPwdPath,
 
- 		Error:          err,
 
- 		CSRFToken:      createCSRFToken(w, r, s.csrfTokenAuth, xid.New().String(), webBaseAdminPath),
 
- 		LoginURL:       webAdminLoginPath,
 
- 		Title:          util.I18nForgotPwdTitle,
 
- 		Branding:       s.binding.webAdminBranding(),
 
- 	}
 
- 	renderAdminTemplate(w, templateForgotPassword, data)
 
- }
 
- func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError) {
 
- 	data := resetPwdPage{
 
- 		commonBasePage: getCommonBasePage(r),
 
- 		CurrentURL:     webAdminResetPwdPath,
 
- 		Error:          err,
 
- 		CSRFToken:      createCSRFToken(w, r, s.csrfTokenAuth, "", webBaseAdminPath),
 
- 		LoginURL:       webAdminLoginPath,
 
- 		Title:          util.I18nResetPwdTitle,
 
- 		Branding:       s.binding.webAdminBranding(),
 
- 	}
 
- 	renderAdminTemplate(w, templateResetPassword, data)
 
- }
 
- func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, r *http.Request, err *util.I18nError) {
 
- 	data := twoFactorPage{
 
- 		commonBasePage: getCommonBasePage(r),
 
- 		Title:          pageTwoFactorTitle,
 
- 		CurrentURL:     webAdminTwoFactorPath,
 
- 		Error:          err,
 
- 		CSRFToken:      createCSRFToken(w, r, s.csrfTokenAuth, "", webBaseAdminPath),
 
- 		RecoveryURL:    webAdminTwoFactorRecoveryPath,
 
- 		Branding:       s.binding.webAdminBranding(),
 
- 	}
 
- 	renderAdminTemplate(w, templateTwoFactor, data)
 
- }
 
- func (s *httpdServer) renderTwoFactorRecoveryPage(w http.ResponseWriter, r *http.Request, err *util.I18nError) {
 
- 	data := twoFactorPage{
 
- 		commonBasePage: getCommonBasePage(r),
 
- 		Title:          pageTwoFactorRecoveryTitle,
 
- 		CurrentURL:     webAdminTwoFactorRecoveryPath,
 
- 		Error:          err,
 
- 		CSRFToken:      createCSRFToken(w, r, s.csrfTokenAuth, "", webBaseAdminPath),
 
- 		Branding:       s.binding.webAdminBranding(),
 
- 	}
 
- 	renderAdminTemplate(w, templateTwoFactorRecovery, data)
 
- }
 
- func (s *httpdServer) renderMFAPage(w http.ResponseWriter, r *http.Request) {
 
- 	data := mfaPage{
 
- 		basePage:        s.getBasePageData(pageMFATitle, webAdminMFAPath, w, r),
 
- 		TOTPConfigs:     mfa.GetAvailableTOTPConfigNames(),
 
- 		GenerateTOTPURL: webAdminTOTPGeneratePath,
 
- 		ValidateTOTPURL: webAdminTOTPValidatePath,
 
- 		SaveTOTPURL:     webAdminTOTPSavePath,
 
- 		RecCodesURL:     webAdminRecoveryCodesPath,
 
- 	}
 
- 	admin, err := dataprovider.AdminExists(data.LoggedUser.Username)
 
- 	if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	data.TOTPConfig = admin.Filters.TOTPConfig
 
- 	data.RequireTwoFactor = admin.Filters.RequireTwoFactor
 
- 	renderAdminTemplate(w, templateMFA, data)
 
- }
 
- func (s *httpdServer) renderProfilePage(w http.ResponseWriter, r *http.Request, err error) {
 
- 	data := profilePage{
 
- 		basePage: s.getBasePageData(util.I18nProfileTitle, webAdminProfilePath, w, r),
 
- 		Error:    getI18nError(err),
 
- 	}
 
- 	admin, err := dataprovider.AdminExists(data.LoggedUser.Username)
 
- 	if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	data.AllowAPIKeyAuth = admin.Filters.AllowAPIKeyAuth
 
- 	data.Email = admin.Email
 
- 	data.Description = admin.Description
 
- 	renderAdminTemplate(w, templateProfile, data)
 
- }
 
- func (s *httpdServer) renderChangePasswordPage(w http.ResponseWriter, r *http.Request, err *util.I18nError) {
 
- 	data := changePasswordPage{
 
- 		basePage: s.getBasePageData(util.I18nChangePwdTitle, webChangeAdminPwdPath, w, r),
 
- 		Error:    err,
 
- 	}
 
- 	renderAdminTemplate(w, templateChangePwd, data)
 
- }
 
- func (s *httpdServer) renderMaintenancePage(w http.ResponseWriter, r *http.Request, err error) {
 
- 	data := maintenancePage{
 
- 		basePage:    s.getBasePageData(util.I18nMaintenanceTitle, webMaintenancePath, w, r),
 
- 		BackupPath:  webBackupPath,
 
- 		RestorePath: webRestorePath,
 
- 		Error:       getI18nError(err),
 
- 	}
 
- 	renderAdminTemplate(w, templateMaintenance, data)
 
- }
 
- func (s *httpdServer) renderConfigsPage(w http.ResponseWriter, r *http.Request, configs dataprovider.Configs,
 
- 	err error, section int,
 
- ) {
 
- 	configs.SetNilsToEmpty()
 
- 	if configs.SMTP.Port == 0 {
 
- 		configs.SMTP.Port = 587
 
- 		configs.SMTP.AuthType = 1
 
- 		configs.SMTP.Encryption = 2
 
- 	}
 
- 	if configs.ACME.HTTP01Challenge.Port == 0 {
 
- 		configs.ACME.HTTP01Challenge.Port = 80
 
- 	}
 
- 	data := configsPage{
 
- 		basePage:          s.getBasePageData(util.I18nConfigsTitle, webConfigsPath, w, r),
 
- 		Configs:           configs,
 
- 		ConfigSection:     section,
 
- 		RedactedSecret:    redactedSecret,
 
- 		OAuth2TokenURL:    webOAuth2TokenPath,
 
- 		OAuth2RedirectURL: webOAuth2RedirectPath,
 
- 		WebClientBranding: s.binding.webClientBranding(),
 
- 		Error:             getI18nError(err),
 
- 	}
 
- 	renderAdminTemplate(w, templateConfigs, data)
 
- }
 
- func (s *httpdServer) renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username string, err *util.I18nError) {
 
- 	data := setupPage{
 
- 		commonBasePage:       getCommonBasePage(r),
 
- 		Title:                util.I18nSetupTitle,
 
- 		CurrentURL:           webAdminSetupPath,
 
- 		CSRFToken:            createCSRFToken(w, r, s.csrfTokenAuth, xid.New().String(), webBaseAdminPath),
 
- 		Username:             username,
 
- 		HasInstallationCode:  installationCode != "",
 
- 		InstallationCodeHint: installationCodeHint,
 
- 		HideSupportLink:      hideSupportLink,
 
- 		Error:                err,
 
- 		Branding:             s.binding.webAdminBranding(),
 
- 	}
 
- 	renderAdminTemplate(w, templateSetup, data)
 
- }
 
- func (s *httpdServer) renderAddUpdateAdminPage(w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin,
 
- 	err error, isAdd bool) {
 
- 	groups, errGroups := s.getWebGroups(w, r, defaultQueryLimit, true)
 
- 	if errGroups != nil {
 
- 		return
 
- 	}
 
- 	roles, errRoles := s.getWebRoles(w, r, 10, true)
 
- 	if errRoles != nil {
 
- 		return
 
- 	}
 
- 	currentURL := webAdminPath
 
- 	title := util.I18nAddAdminTitle
 
- 	if !isAdd {
 
- 		currentURL = fmt.Sprintf("%v/%v", webAdminPath, url.PathEscape(admin.Username))
 
- 		title = util.I18nUpdateAdminTitle
 
- 	}
 
- 	data := adminPage{
 
- 		basePage: s.getBasePageData(title, currentURL, w, r),
 
- 		Admin:    admin,
 
- 		Groups:   groups,
 
- 		Roles:    roles,
 
- 		Error:    getI18nError(err),
 
- 		IsAdd:    isAdd,
 
- 	}
 
- 	renderAdminTemplate(w, templateAdmin, data)
 
- }
 
- func (s *httpdServer) getUserPageTitleAndURL(mode userPageMode, username string) (string, string) {
 
- 	var title, currentURL string
 
- 	switch mode {
 
- 	case userPageModeAdd:
 
- 		title = util.I18nAddUserTitle
 
- 		currentURL = webUserPath
 
- 	case userPageModeUpdate:
 
- 		title = util.I18nUpdateUserTitle
 
- 		currentURL = fmt.Sprintf("%v/%v", webUserPath, url.PathEscape(username))
 
- 	case userPageModeTemplate:
 
- 		title = util.I18nTemplateUserTitle
 
- 		currentURL = webTemplateUser
 
- 	}
 
- 	return title, currentURL
 
- }
 
- func (s *httpdServer) renderUserPage(w http.ResponseWriter, r *http.Request, user *dataprovider.User,
 
- 	mode userPageMode, err error, admin *dataprovider.Admin,
 
- ) {
 
- 	user.SetEmptySecretsIfNil()
 
- 	title, currentURL := s.getUserPageTitleAndURL(mode, user.Username)
 
- 	if user.Password != "" && user.IsPasswordHashed() {
 
- 		switch mode {
 
- 		case userPageModeUpdate:
 
- 			user.Password = redactedSecret
 
- 		default:
 
- 			user.Password = ""
 
- 		}
 
- 	}
 
- 	user.FsConfig.RedactedSecret = redactedSecret
 
- 	basePage := s.getBasePageData(title, currentURL, w, r)
 
- 	if (mode == userPageModeAdd || mode == userPageModeTemplate) && len(user.Groups) == 0 && admin != nil {
 
- 		for _, group := range admin.Groups {
 
- 			user.Groups = append(user.Groups, sdk.GroupMapping{
 
- 				Name: group.Name,
 
- 				Type: group.Options.GetUserGroupType(),
 
- 			})
 
- 		}
 
- 	}
 
- 	var roles []dataprovider.Role
 
- 	if basePage.LoggedUser.Role == "" {
 
- 		var errRoles error
 
- 		roles, errRoles = s.getWebRoles(w, r, 10, true)
 
- 		if errRoles != nil {
 
- 			return
 
- 		}
 
- 	}
 
- 	folders, errFolders := s.getWebVirtualFolders(w, r, defaultQueryLimit, true)
 
- 	if errFolders != nil {
 
- 		return
 
- 	}
 
- 	groups, errGroups := s.getWebGroups(w, r, defaultQueryLimit, true)
 
- 	if errGroups != nil {
 
- 		return
 
- 	}
 
- 	data := userPage{
 
- 		basePage:           basePage,
 
- 		Mode:               mode,
 
- 		Error:              getI18nError(err),
 
- 		User:               user,
 
- 		ValidPerms:         dataprovider.ValidPerms,
 
- 		ValidLoginMethods:  dataprovider.ValidLoginMethods,
 
- 		ValidProtocols:     dataprovider.ValidProtocols,
 
- 		TwoFactorProtocols: dataprovider.MFAProtocols,
 
- 		WebClientOptions:   sdk.WebClientOptions,
 
- 		RootDirPerms:       user.GetPermissionsForPath("/"),
 
- 		VirtualFolders:     folders,
 
- 		Groups:             groups,
 
- 		Roles:              roles,
 
- 		CanImpersonate:     os.Getuid() == 0,
 
- 		FsWrapper: fsWrapper{
 
- 			Filesystem:      user.FsConfig,
 
- 			IsUserPage:      true,
 
- 			IsGroupPage:     false,
 
- 			IsHidden:        basePage.LoggedUser.Filters.Preferences.HideFilesystem(),
 
- 			HasUsersBaseDir: dataprovider.HasUsersBaseDir(),
 
- 			DirPath:         user.HomeDir,
 
- 		},
 
- 	}
 
- 	renderAdminTemplate(w, templateUser, data)
 
- }
 
- func (s *httpdServer) renderIPListPage(w http.ResponseWriter, r *http.Request, entry dataprovider.IPListEntry,
 
- 	mode genericPageMode, err error,
 
- ) {
 
- 	var title, currentURL string
 
- 	switch mode {
 
- 	case genericPageModeAdd:
 
- 		title = util.I18nAddIPListTitle
 
- 		currentURL = fmt.Sprintf("%s/%d", webIPListPath, entry.Type)
 
- 	case genericPageModeUpdate:
 
- 		title = util.I18nUpdateIPListTitle
 
- 		currentURL = fmt.Sprintf("%s/%d/%s", webIPListPath, entry.Type, url.PathEscape(entry.IPOrNet))
 
- 	}
 
- 	data := ipListPage{
 
- 		basePage: s.getBasePageData(title, currentURL, w, r),
 
- 		Error:    getI18nError(err),
 
- 		Entry:    &entry,
 
- 		Mode:     mode,
 
- 	}
 
- 	renderAdminTemplate(w, templateIPList, data)
 
- }
 
- func (s *httpdServer) renderRolePage(w http.ResponseWriter, r *http.Request, role dataprovider.Role,
 
- 	mode genericPageMode, err error,
 
- ) {
 
- 	var title, currentURL string
 
- 	switch mode {
 
- 	case genericPageModeAdd:
 
- 		title = util.I18nRoleAddTitle
 
- 		currentURL = webAdminRolePath
 
- 	case genericPageModeUpdate:
 
- 		title = util.I18nRoleUpdateTitle
 
- 		currentURL = fmt.Sprintf("%s/%s", webAdminRolePath, url.PathEscape(role.Name))
 
- 	}
 
- 	data := rolePage{
 
- 		basePage: s.getBasePageData(title, currentURL, w, r),
 
- 		Error:    getI18nError(err),
 
- 		Role:     &role,
 
- 		Mode:     mode,
 
- 	}
 
- 	renderAdminTemplate(w, templateRole, data)
 
- }
 
- func (s *httpdServer) renderGroupPage(w http.ResponseWriter, r *http.Request, group dataprovider.Group,
 
- 	mode genericPageMode, err error,
 
- ) {
 
- 	folders, errFolders := s.getWebVirtualFolders(w, r, defaultQueryLimit, true)
 
- 	if errFolders != nil {
 
- 		return
 
- 	}
 
- 	group.SetEmptySecretsIfNil()
 
- 	group.UserSettings.FsConfig.RedactedSecret = redactedSecret
 
- 	var title, currentURL string
 
- 	switch mode {
 
- 	case genericPageModeAdd:
 
- 		title = util.I18nAddGroupTitle
 
- 		currentURL = webGroupPath
 
- 	case genericPageModeUpdate:
 
- 		title = util.I18nUpdateGroupTitle
 
- 		currentURL = fmt.Sprintf("%v/%v", webGroupPath, url.PathEscape(group.Name))
 
- 	}
 
- 	group.UserSettings.FsConfig.RedactedSecret = redactedSecret
 
- 	group.UserSettings.FsConfig.SetEmptySecretsIfNil()
 
- 	data := groupPage{
 
- 		basePage:           s.getBasePageData(title, currentURL, w, r),
 
- 		Error:              getI18nError(err),
 
- 		Group:              &group,
 
- 		Mode:               mode,
 
- 		ValidPerms:         dataprovider.ValidPerms,
 
- 		ValidLoginMethods:  dataprovider.ValidLoginMethods,
 
- 		ValidProtocols:     dataprovider.ValidProtocols,
 
- 		TwoFactorProtocols: dataprovider.MFAProtocols,
 
- 		WebClientOptions:   sdk.WebClientOptions,
 
- 		VirtualFolders:     folders,
 
- 		FsWrapper: fsWrapper{
 
- 			Filesystem:      group.UserSettings.FsConfig,
 
- 			IsUserPage:      false,
 
- 			IsGroupPage:     true,
 
- 			HasUsersBaseDir: false,
 
- 			DirPath:         group.UserSettings.HomeDir,
 
- 		},
 
- 	}
 
- 	renderAdminTemplate(w, templateGroup, data)
 
- }
 
- func (s *httpdServer) renderEventActionPage(w http.ResponseWriter, r *http.Request, action dataprovider.BaseEventAction,
 
- 	mode genericPageMode, err error,
 
- ) {
 
- 	action.Options.SetEmptySecretsIfNil()
 
- 	var title, currentURL string
 
- 	switch mode {
 
- 	case genericPageModeAdd:
 
- 		title = util.I18nAddActionTitle
 
- 		currentURL = webAdminEventActionPath
 
- 	case genericPageModeUpdate:
 
- 		title = util.I18nUpdateActionTitle
 
- 		currentURL = fmt.Sprintf("%s/%s", webAdminEventActionPath, url.PathEscape(action.Name))
 
- 	}
 
- 	if action.Options.HTTPConfig.Timeout == 0 {
 
- 		action.Options.HTTPConfig.Timeout = 20
 
- 	}
 
- 	if action.Options.CmdConfig.Timeout == 0 {
 
- 		action.Options.CmdConfig.Timeout = 20
 
- 	}
 
- 	if action.Options.PwdExpirationConfig.Threshold == 0 {
 
- 		action.Options.PwdExpirationConfig.Threshold = 10
 
- 	}
 
- 	data := eventActionPage{
 
- 		basePage:       s.getBasePageData(title, currentURL, w, r),
 
- 		Action:         action,
 
- 		ActionTypes:    dataprovider.EventActionTypes,
 
- 		FsActions:      dataprovider.FsActionTypes,
 
- 		HTTPMethods:    dataprovider.SupportedHTTPActionMethods,
 
- 		RedactedSecret: redactedSecret,
 
- 		Error:          getI18nError(err),
 
- 		Mode:           mode,
 
- 	}
 
- 	renderAdminTemplate(w, templateEventAction, data)
 
- }
 
- func (s *httpdServer) renderEventRulePage(w http.ResponseWriter, r *http.Request, rule dataprovider.EventRule,
 
- 	mode genericPageMode, err error,
 
- ) {
 
- 	actions, errActions := s.getWebEventActions(w, r, defaultQueryLimit, true)
 
- 	if errActions != nil {
 
- 		return
 
- 	}
 
- 	var title, currentURL string
 
- 	switch mode {
 
- 	case genericPageModeAdd:
 
- 		title = util.I18nAddRuleTitle
 
- 		currentURL = webAdminEventRulePath
 
- 	case genericPageModeUpdate:
 
- 		title = util.I18nUpdateRuleTitle
 
- 		currentURL = fmt.Sprintf("%v/%v", webAdminEventRulePath, url.PathEscape(rule.Name))
 
- 	}
 
- 	data := eventRulePage{
 
- 		basePage:        s.getBasePageData(title, currentURL, w, r),
 
- 		Rule:            rule,
 
- 		TriggerTypes:    dataprovider.EventTriggerTypes,
 
- 		Actions:         actions,
 
- 		FsEvents:        dataprovider.SupportedFsEvents,
 
- 		Protocols:       dataprovider.SupportedRuleConditionProtocols,
 
- 		ProviderEvents:  dataprovider.SupportedProviderEvents,
 
- 		ProviderObjects: dataprovider.SupporteRuleConditionProviderObjects,
 
- 		Error:           getI18nError(err),
 
- 		Mode:            mode,
 
- 		IsShared:        s.isShared > 0,
 
- 	}
 
- 	renderAdminTemplate(w, templateEventRule, data)
 
- }
 
- func (s *httpdServer) renderFolderPage(w http.ResponseWriter, r *http.Request, folder vfs.BaseVirtualFolder,
 
- 	mode folderPageMode, err error,
 
- ) {
 
- 	var title, currentURL string
 
- 	switch mode {
 
- 	case folderPageModeAdd:
 
- 		title = util.I18nAddFolderTitle
 
- 		currentURL = webFolderPath
 
- 	case folderPageModeUpdate:
 
- 		title = util.I18nUpdateFolderTitle
 
- 		currentURL = fmt.Sprintf("%v/%v", webFolderPath, url.PathEscape(folder.Name))
 
- 	case folderPageModeTemplate:
 
- 		title = util.I18nTemplateFolderTitle
 
- 		currentURL = webTemplateFolder
 
- 	}
 
- 	folder.FsConfig.RedactedSecret = redactedSecret
 
- 	folder.FsConfig.SetEmptySecretsIfNil()
 
- 	data := folderPage{
 
- 		basePage: s.getBasePageData(title, currentURL, w, r),
 
- 		Error:    getI18nError(err),
 
- 		Folder:   folder,
 
- 		Mode:     mode,
 
- 		FsWrapper: fsWrapper{
 
- 			Filesystem:      folder.FsConfig,
 
- 			IsUserPage:      false,
 
- 			IsGroupPage:     false,
 
- 			HasUsersBaseDir: false,
 
- 			DirPath:         folder.MappedPath,
 
- 		},
 
- 	}
 
- 	renderAdminTemplate(w, templateFolder, data)
 
- }
 
- func getFoldersForTemplate(r *http.Request) []string {
 
- 	var res []string
 
- 	for k := range r.Form {
 
- 		if hasPrefixAndSuffix(k, "template_folders[", "][tpl_foldername]") {
 
- 			r.Form.Add("tpl_foldername", r.Form.Get(k))
 
- 		}
 
- 	}
 
- 	folderNames := r.Form["tpl_foldername"]
 
- 	folders := make(map[string]bool)
 
- 	for _, name := range folderNames {
 
- 		name = strings.TrimSpace(name)
 
- 		if name == "" {
 
- 			continue
 
- 		}
 
- 		if _, ok := folders[name]; ok {
 
- 			continue
 
- 		}
 
- 		folders[name] = true
 
- 		res = append(res, name)
 
- 	}
 
- 	return res
 
- }
 
- func getUsersForTemplate(r *http.Request) []userTemplateFields {
 
- 	var res []userTemplateFields
 
- 	tplUsernames := r.Form["tpl_username"]
 
- 	tplPasswords := r.Form["tpl_password"]
 
- 	tplPublicKeys := r.Form["tpl_public_keys"]
 
- 	users := make(map[string]bool)
 
- 	for idx := range tplUsernames {
 
- 		username := tplUsernames[idx]
 
- 		password := ""
 
- 		publicKey := ""
 
- 		if len(tplPasswords) > idx {
 
- 			password = strings.TrimSpace(tplPasswords[idx])
 
- 		}
 
- 		if len(tplPublicKeys) > idx {
 
- 			publicKey = strings.TrimSpace(tplPublicKeys[idx])
 
- 		}
 
- 		if username == "" {
 
- 			continue
 
- 		}
 
- 		if _, ok := users[username]; ok {
 
- 			continue
 
- 		}
 
- 		users[username] = true
 
- 		res = append(res, userTemplateFields{
 
- 			Username:   username,
 
- 			Password:   password,
 
- 			PublicKeys: []string{publicKey},
 
- 		})
 
- 	}
 
- 	return res
 
- }
 
- func getVirtualFoldersFromPostFields(r *http.Request) []vfs.VirtualFolder {
 
- 	var virtualFolders []vfs.VirtualFolder
 
- 	folderPaths := r.Form["vfolder_path"]
 
- 	folderNames := r.Form["vfolder_name"]
 
- 	folderQuotaSizes := r.Form["vfolder_quota_size"]
 
- 	folderQuotaFiles := r.Form["vfolder_quota_files"]
 
- 	for idx, p := range folderPaths {
 
- 		name := ""
 
- 		if len(folderNames) > idx {
 
- 			name = folderNames[idx]
 
- 		}
 
- 		if p != "" && name != "" {
 
- 			vfolder := vfs.VirtualFolder{
 
- 				BaseVirtualFolder: vfs.BaseVirtualFolder{
 
- 					Name: name,
 
- 				},
 
- 				VirtualPath: p,
 
- 				QuotaFiles:  -1,
 
- 				QuotaSize:   -1,
 
- 			}
 
- 			if len(folderQuotaSizes) > idx {
 
- 				quotaSize, err := util.ParseBytes(folderQuotaSizes[idx])
 
- 				if err == nil {
 
- 					vfolder.QuotaSize = quotaSize
 
- 				}
 
- 			}
 
- 			if len(folderQuotaFiles) > idx {
 
- 				quotaFiles, err := strconv.Atoi(folderQuotaFiles[idx])
 
- 				if err == nil {
 
- 					vfolder.QuotaFiles = quotaFiles
 
- 				}
 
- 			}
 
- 			virtualFolders = append(virtualFolders, vfolder)
 
- 		}
 
- 	}
 
- 	return virtualFolders
 
- }
 
- func getSubDirPermissionsFromPostFields(r *http.Request) map[string][]string {
 
- 	permissions := make(map[string][]string)
 
- 	for idx, p := range r.Form["sub_perm_path"] {
 
- 		if p != "" {
 
- 			permissions[p] = r.Form["sub_perm_permissions"+strconv.Itoa(idx)]
 
- 		}
 
- 	}
 
- 	return permissions
 
- }
 
- func getUserPermissionsFromPostFields(r *http.Request) map[string][]string {
 
- 	permissions := getSubDirPermissionsFromPostFields(r)
 
- 	permissions["/"] = r.Form["permissions"]
 
- 	return permissions
 
- }
 
- func getAccessTimeRestrictionsFromPostFields(r *http.Request) []sdk.TimePeriod {
 
- 	var result []sdk.TimePeriod
 
- 	dayOfWeeks := r.Form["access_time_day_of_week"]
 
- 	starts := r.Form["access_time_start"]
 
- 	ends := r.Form["access_time_end"]
 
- 	for idx, dayOfWeek := range dayOfWeeks {
 
- 		dayOfWeek = strings.TrimSpace(dayOfWeek)
 
- 		start := ""
 
- 		if len(starts) > idx {
 
- 			start = strings.TrimSpace(starts[idx])
 
- 		}
 
- 		end := ""
 
- 		if len(ends) > idx {
 
- 			end = strings.TrimSpace(ends[idx])
 
- 		}
 
- 		dayNumber, err := strconv.Atoi(dayOfWeek)
 
- 		if err == nil && start != "" && end != "" {
 
- 			result = append(result, sdk.TimePeriod{
 
- 				DayOfWeek: dayNumber,
 
- 				From:      start,
 
- 				To:        end,
 
- 			})
 
- 		}
 
- 	}
 
- 	return result
 
- }
 
- func getBandwidthLimitsFromPostFields(r *http.Request) ([]sdk.BandwidthLimit, error) {
 
- 	var result []sdk.BandwidthLimit
 
- 	bwSources := r.Form["bandwidth_limit_sources"]
 
- 	uploadSources := r.Form["upload_bandwidth_source"]
 
- 	downloadSources := r.Form["download_bandwidth_source"]
 
- 	for idx, bwSource := range bwSources {
 
- 		sources := getSliceFromDelimitedValues(bwSource, ",")
 
- 		if len(sources) > 0 {
 
- 			bwLimit := sdk.BandwidthLimit{
 
- 				Sources: sources,
 
- 			}
 
- 			ul := ""
 
- 			dl := ""
 
- 			if len(uploadSources) > idx {
 
- 				ul = uploadSources[idx]
 
- 			}
 
- 			if len(downloadSources) > idx {
 
- 				dl = downloadSources[idx]
 
- 			}
 
- 			if ul != "" {
 
- 				bandwidthUL, err := strconv.ParseInt(ul, 10, 64)
 
- 				if err != nil {
 
- 					return result, fmt.Errorf("invalid upload_bandwidth_source%v %q: %w", idx, ul, err)
 
- 				}
 
- 				bwLimit.UploadBandwidth = bandwidthUL
 
- 			}
 
- 			if dl != "" {
 
- 				bandwidthDL, err := strconv.ParseInt(dl, 10, 64)
 
- 				if err != nil {
 
- 					return result, fmt.Errorf("invalid download_bandwidth_source%v %q: %w", idx, ul, err)
 
- 				}
 
- 				bwLimit.DownloadBandwidth = bandwidthDL
 
- 			}
 
- 			result = append(result, bwLimit)
 
- 		}
 
- 	}
 
- 	return result, nil
 
- }
 
- func getPatterDenyPolicyFromString(policy string) int {
 
- 	denyPolicy := sdk.DenyPolicyDefault
 
- 	if policy == "1" {
 
- 		denyPolicy = sdk.DenyPolicyHide
 
- 	}
 
- 	return denyPolicy
 
- }
 
- func getFilePatternsFromPostField(r *http.Request) []sdk.PatternsFilter {
 
- 	var result []sdk.PatternsFilter
 
- 	patternPaths := r.Form["pattern_path"]
 
- 	patterns := r.Form["patterns"]
 
- 	patternTypes := r.Form["pattern_type"]
 
- 	policies := r.Form["pattern_policy"]
 
- 	allowedPatterns := make(map[string][]string)
 
- 	deniedPatterns := make(map[string][]string)
 
- 	patternPolicies := make(map[string]string)
 
- 	for idx := range patternPaths {
 
- 		p := patternPaths[idx]
 
- 		filters := strings.ReplaceAll(patterns[idx], " ", "")
 
- 		patternType := patternTypes[idx]
 
- 		patternPolicy := policies[idx]
 
- 		if p != "" && filters != "" {
 
- 			if patternType == "allowed" {
 
- 				allowedPatterns[p] = append(allowedPatterns[p], strings.Split(filters, ",")...)
 
- 			} else {
 
- 				deniedPatterns[p] = append(deniedPatterns[p], strings.Split(filters, ",")...)
 
- 			}
 
- 			if patternPolicy != "" && patternPolicy != "0" {
 
- 				patternPolicies[p] = patternPolicy
 
- 			}
 
- 		}
 
- 	}
 
- 	for dirAllowed, allowPatterns := range allowedPatterns {
 
- 		filter := sdk.PatternsFilter{
 
- 			Path:            dirAllowed,
 
- 			AllowedPatterns: allowPatterns,
 
- 			DenyPolicy:      getPatterDenyPolicyFromString(patternPolicies[dirAllowed]),
 
- 		}
 
- 		for dirDenied, denPatterns := range deniedPatterns {
 
- 			if dirAllowed == dirDenied {
 
- 				filter.DeniedPatterns = denPatterns
 
- 				break
 
- 			}
 
- 		}
 
- 		result = append(result, filter)
 
- 	}
 
- 	for dirDenied, denPatterns := range deniedPatterns {
 
- 		found := false
 
- 		for _, res := range result {
 
- 			if res.Path == dirDenied {
 
- 				found = true
 
- 				break
 
- 			}
 
- 		}
 
- 		if !found {
 
- 			result = append(result, sdk.PatternsFilter{
 
- 				Path:           dirDenied,
 
- 				DeniedPatterns: denPatterns,
 
- 				DenyPolicy:     getPatterDenyPolicyFromString(patternPolicies[dirDenied]),
 
- 			})
 
- 		}
 
- 	}
 
- 	return result
 
- }
 
- func getGroupsFromUserPostFields(r *http.Request) []sdk.GroupMapping {
 
- 	var groups []sdk.GroupMapping
 
- 	primaryGroup := strings.TrimSpace(r.Form.Get("primary_group"))
 
- 	if primaryGroup != "" {
 
- 		groups = append(groups, sdk.GroupMapping{
 
- 			Name: primaryGroup,
 
- 			Type: sdk.GroupTypePrimary,
 
- 		})
 
- 	}
 
- 	secondaryGroups := r.Form["secondary_groups"]
 
- 	for _, name := range secondaryGroups {
 
- 		groups = append(groups, sdk.GroupMapping{
 
- 			Name: strings.TrimSpace(name),
 
- 			Type: sdk.GroupTypeSecondary,
 
- 		})
 
- 	}
 
- 	membershipGroups := r.Form["membership_groups"]
 
- 	for _, name := range membershipGroups {
 
- 		groups = append(groups, sdk.GroupMapping{
 
- 			Name: strings.TrimSpace(name),
 
- 			Type: sdk.GroupTypeMembership,
 
- 		})
 
- 	}
 
- 	return groups
 
- }
 
- func getFiltersFromUserPostFields(r *http.Request) (sdk.BaseUserFilters, error) {
 
- 	var filters sdk.BaseUserFilters
 
- 	bwLimits, err := getBandwidthLimitsFromPostFields(r)
 
- 	if err != nil {
 
- 		return filters, err
 
- 	}
 
- 	maxFileSize, err := util.ParseBytes(r.Form.Get("max_upload_file_size"))
 
- 	if err != nil {
 
- 		return filters, util.NewI18nError(fmt.Errorf("invalid max upload file size: %w", err), util.I18nErrorInvalidMaxFilesize)
 
- 	}
 
- 	defaultSharesExpiration, err := strconv.Atoi(r.Form.Get("default_shares_expiration"))
 
- 	if err != nil {
 
- 		return filters, fmt.Errorf("invalid default shares expiration: %w", err)
 
- 	}
 
- 	maxSharesExpiration, err := strconv.Atoi(r.Form.Get("max_shares_expiration"))
 
- 	if err != nil {
 
- 		return filters, fmt.Errorf("invalid max shares expiration: %w", err)
 
- 	}
 
- 	passwordExpiration, err := strconv.Atoi(r.Form.Get("password_expiration"))
 
- 	if err != nil {
 
- 		return filters, fmt.Errorf("invalid password expiration: %w", err)
 
- 	}
 
- 	passwordStrength, err := strconv.Atoi(r.Form.Get("password_strength"))
 
- 	if err != nil {
 
- 		return filters, fmt.Errorf("invalid password strength: %w", err)
 
- 	}
 
- 	if r.Form.Get("ftp_security") == "1" {
 
- 		filters.FTPSecurity = 1
 
- 	}
 
- 	filters.BandwidthLimits = bwLimits
 
- 	filters.AllowedIP = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
 
- 	filters.DeniedIP = getSliceFromDelimitedValues(r.Form.Get("denied_ip"), ",")
 
- 	filters.DeniedLoginMethods = r.Form["denied_login_methods"]
 
- 	filters.DeniedProtocols = r.Form["denied_protocols"]
 
- 	filters.TwoFactorAuthProtocols = r.Form["required_two_factor_protocols"]
 
- 	filters.FilePatterns = getFilePatternsFromPostField(r)
 
- 	filters.TLSUsername = sdk.TLSUsername(strings.TrimSpace(r.Form.Get("tls_username")))
 
- 	filters.WebClient = r.Form["web_client_options"]
 
- 	filters.DefaultSharesExpiration = defaultSharesExpiration
 
- 	filters.MaxSharesExpiration = maxSharesExpiration
 
- 	filters.PasswordExpiration = passwordExpiration
 
- 	filters.PasswordStrength = passwordStrength
 
- 	filters.AccessTime = getAccessTimeRestrictionsFromPostFields(r)
 
- 	hooks := r.Form["hooks"]
 
- 	if slices.Contains(hooks, "external_auth_disabled") {
 
- 		filters.Hooks.ExternalAuthDisabled = true
 
- 	}
 
- 	if slices.Contains(hooks, "pre_login_disabled") {
 
- 		filters.Hooks.PreLoginDisabled = true
 
- 	}
 
- 	if slices.Contains(hooks, "check_password_disabled") {
 
- 		filters.Hooks.CheckPasswordDisabled = true
 
- 	}
 
- 	filters.IsAnonymous = r.Form.Get("is_anonymous") != ""
 
- 	filters.DisableFsChecks = r.Form.Get("disable_fs_checks") != ""
 
- 	filters.AllowAPIKeyAuth = r.Form.Get("allow_api_key_auth") != ""
 
- 	filters.StartDirectory = strings.TrimSpace(r.Form.Get("start_directory"))
 
- 	filters.MaxUploadFileSize = maxFileSize
 
- 	filters.ExternalAuthCacheTime, err = strconv.ParseInt(r.Form.Get("external_auth_cache_time"), 10, 64)
 
- 	if err != nil {
 
- 		return filters, fmt.Errorf("invalid external auth cache time: %w", err)
 
- 	}
 
- 	return filters, nil
 
- }
 
- func getSecretFromFormField(r *http.Request, field string) *kms.Secret {
 
- 	secret := kms.NewPlainSecret(r.Form.Get(field))
 
- 	if strings.TrimSpace(secret.GetPayload()) == redactedSecret {
 
- 		secret.SetStatus(sdkkms.SecretStatusRedacted)
 
- 	}
 
- 	if strings.TrimSpace(secret.GetPayload()) == "" {
 
- 		secret.SetStatus("")
 
- 	}
 
- 	return secret
 
- }
 
- func getS3Config(r *http.Request) (vfs.S3FsConfig, error) {
 
- 	var err error
 
- 	config := vfs.S3FsConfig{}
 
- 	config.Bucket = strings.TrimSpace(r.Form.Get("s3_bucket"))
 
- 	config.Region = strings.TrimSpace(r.Form.Get("s3_region"))
 
- 	config.AccessKey = strings.TrimSpace(r.Form.Get("s3_access_key"))
 
- 	config.RoleARN = strings.TrimSpace(r.Form.Get("s3_role_arn"))
 
- 	config.AccessSecret = getSecretFromFormField(r, "s3_access_secret")
 
- 	config.SSECustomerKey = getSecretFromFormField(r, "s3_sse_customer_key")
 
- 	config.Endpoint = strings.TrimSpace(r.Form.Get("s3_endpoint"))
 
- 	config.StorageClass = strings.TrimSpace(r.Form.Get("s3_storage_class"))
 
- 	config.ACL = strings.TrimSpace(r.Form.Get("s3_acl"))
 
- 	config.KeyPrefix = strings.TrimSpace(strings.TrimPrefix(r.Form.Get("s3_key_prefix"), "/"))
 
- 	config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("s3_upload_part_size"), 10, 64)
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid s3 upload part size: %w", err)
 
- 	}
 
- 	config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("s3_upload_concurrency"))
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid s3 upload concurrency: %w", err)
 
- 	}
 
- 	config.DownloadPartSize, err = strconv.ParseInt(r.Form.Get("s3_download_part_size"), 10, 64)
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid s3 download part size: %w", err)
 
- 	}
 
- 	config.DownloadConcurrency, err = strconv.Atoi(r.Form.Get("s3_download_concurrency"))
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid s3 download concurrency: %w", err)
 
- 	}
 
- 	config.ForcePathStyle = r.Form.Get("s3_force_path_style") != ""
 
- 	config.SkipTLSVerify = r.Form.Get("s3_skip_tls_verify") != ""
 
- 	config.DownloadPartMaxTime, err = strconv.Atoi(r.Form.Get("s3_download_part_max_time"))
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid s3 download part max time: %w", err)
 
- 	}
 
- 	config.UploadPartMaxTime, err = strconv.Atoi(r.Form.Get("s3_upload_part_max_time"))
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid s3 upload part max time: %w", err)
 
- 	}
 
- 	return config, nil
 
- }
 
- func getGCSConfig(r *http.Request) (vfs.GCSFsConfig, error) {
 
- 	var err error
 
- 	config := vfs.GCSFsConfig{}
 
- 	config.Bucket = strings.TrimSpace(r.Form.Get("gcs_bucket"))
 
- 	config.StorageClass = strings.TrimSpace(r.Form.Get("gcs_storage_class"))
 
- 	config.ACL = strings.TrimSpace(r.Form.Get("gcs_acl"))
 
- 	config.KeyPrefix = strings.TrimSpace(strings.TrimPrefix(r.Form.Get("gcs_key_prefix"), "/"))
 
- 	uploadPartSize, err := strconv.ParseInt(r.Form.Get("gcs_upload_part_size"), 10, 64)
 
- 	if err == nil {
 
- 		config.UploadPartSize = uploadPartSize
 
- 	}
 
- 	uploadPartMaxTime, err := strconv.Atoi(r.Form.Get("gcs_upload_part_max_time"))
 
- 	if err == nil {
 
- 		config.UploadPartMaxTime = uploadPartMaxTime
 
- 	}
 
- 	autoCredentials := r.Form.Get("gcs_auto_credentials")
 
- 	if autoCredentials != "" {
 
- 		config.AutomaticCredentials = 1
 
- 	} else {
 
- 		config.AutomaticCredentials = 0
 
- 	}
 
- 	credentials, _, err := r.FormFile("gcs_credential_file")
 
- 	if errors.Is(err, http.ErrMissingFile) {
 
- 		return config, nil
 
- 	}
 
- 	if err != nil {
 
- 		return config, err
 
- 	}
 
- 	defer credentials.Close()
 
- 	fileBytes, err := io.ReadAll(credentials)
 
- 	if err != nil || len(fileBytes) == 0 {
 
- 		if len(fileBytes) == 0 {
 
- 			err = errors.New("credentials file size must be greater than 0")
 
- 		}
 
- 		return config, err
 
- 	}
 
- 	config.Credentials = kms.NewPlainSecret(util.BytesToString(fileBytes))
 
- 	config.AutomaticCredentials = 0
 
- 	return config, err
 
- }
 
- func getSFTPConfig(r *http.Request) (vfs.SFTPFsConfig, error) {
 
- 	var err error
 
- 	config := vfs.SFTPFsConfig{}
 
- 	config.Endpoint = strings.TrimSpace(r.Form.Get("sftp_endpoint"))
 
- 	config.Username = strings.TrimSpace(r.Form.Get("sftp_username"))
 
- 	config.Password = getSecretFromFormField(r, "sftp_password")
 
- 	config.PrivateKey = getSecretFromFormField(r, "sftp_private_key")
 
- 	config.KeyPassphrase = getSecretFromFormField(r, "sftp_key_passphrase")
 
- 	fingerprintsFormValue := r.Form.Get("sftp_fingerprints")
 
- 	config.Fingerprints = getSliceFromDelimitedValues(fingerprintsFormValue, "\n")
 
- 	config.Prefix = strings.TrimSpace(r.Form.Get("sftp_prefix"))
 
- 	config.DisableCouncurrentReads = r.Form.Get("sftp_disable_concurrent_reads") != ""
 
- 	config.BufferSize, err = strconv.ParseInt(r.Form.Get("sftp_buffer_size"), 10, 64)
 
- 	if r.Form.Get("sftp_equality_check_mode") != "" {
 
- 		config.EqualityCheckMode = 1
 
- 	} else {
 
- 		config.EqualityCheckMode = 0
 
- 	}
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid SFTP buffer size: %w", err)
 
- 	}
 
- 	return config, nil
 
- }
 
- func getHTTPFsConfig(r *http.Request) vfs.HTTPFsConfig {
 
- 	config := vfs.HTTPFsConfig{}
 
- 	config.Endpoint = strings.TrimSpace(r.Form.Get("http_endpoint"))
 
- 	config.Username = strings.TrimSpace(r.Form.Get("http_username"))
 
- 	config.SkipTLSVerify = r.Form.Get("http_skip_tls_verify") != ""
 
- 	config.Password = getSecretFromFormField(r, "http_password")
 
- 	config.APIKey = getSecretFromFormField(r, "http_api_key")
 
- 	if r.Form.Get("http_equality_check_mode") != "" {
 
- 		config.EqualityCheckMode = 1
 
- 	} else {
 
- 		config.EqualityCheckMode = 0
 
- 	}
 
- 	return config
 
- }
 
- func getAzureConfig(r *http.Request) (vfs.AzBlobFsConfig, error) {
 
- 	var err error
 
- 	config := vfs.AzBlobFsConfig{}
 
- 	config.Container = strings.TrimSpace(r.Form.Get("az_container"))
 
- 	config.AccountName = strings.TrimSpace(r.Form.Get("az_account_name"))
 
- 	config.AccountKey = getSecretFromFormField(r, "az_account_key")
 
- 	config.SASURL = getSecretFromFormField(r, "az_sas_url")
 
- 	config.Endpoint = strings.TrimSpace(r.Form.Get("az_endpoint"))
 
- 	config.KeyPrefix = strings.TrimSpace(strings.TrimPrefix(r.Form.Get("az_key_prefix"), "/"))
 
- 	config.AccessTier = strings.TrimSpace(r.Form.Get("az_access_tier"))
 
- 	config.UseEmulator = r.Form.Get("az_use_emulator") != ""
 
- 	config.UploadPartSize, err = strconv.ParseInt(r.Form.Get("az_upload_part_size"), 10, 64)
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid azure upload part size: %w", err)
 
- 	}
 
- 	config.UploadConcurrency, err = strconv.Atoi(r.Form.Get("az_upload_concurrency"))
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid azure upload concurrency: %w", err)
 
- 	}
 
- 	config.DownloadPartSize, err = strconv.ParseInt(r.Form.Get("az_download_part_size"), 10, 64)
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid azure download part size: %w", err)
 
- 	}
 
- 	config.DownloadConcurrency, err = strconv.Atoi(r.Form.Get("az_download_concurrency"))
 
- 	if err != nil {
 
- 		return config, fmt.Errorf("invalid azure download concurrency: %w", err)
 
- 	}
 
- 	return config, nil
 
- }
 
- func getOsConfigFromPostFields(r *http.Request, readBufferField, writeBufferField string) sdk.OSFsConfig {
 
- 	config := sdk.OSFsConfig{}
 
- 	readBuffer, err := strconv.Atoi(r.Form.Get(readBufferField))
 
- 	if err == nil {
 
- 		config.ReadBufferSize = readBuffer
 
- 	}
 
- 	writeBuffer, err := strconv.Atoi(r.Form.Get(writeBufferField))
 
- 	if err == nil {
 
- 		config.WriteBufferSize = writeBuffer
 
- 	}
 
- 	return config
 
- }
 
- func getFsConfigFromPostFields(r *http.Request) (vfs.Filesystem, error) {
 
- 	var fs vfs.Filesystem
 
- 	fs.Provider = dataprovider.GetProviderFromValue(r.Form.Get("fs_provider"))
 
- 	switch fs.Provider {
 
- 	case sdk.LocalFilesystemProvider:
 
- 		fs.OSConfig = getOsConfigFromPostFields(r, "osfs_read_buffer_size", "osfs_write_buffer_size")
 
- 	case sdk.S3FilesystemProvider:
 
- 		config, err := getS3Config(r)
 
- 		if err != nil {
 
- 			return fs, err
 
- 		}
 
- 		fs.S3Config = config
 
- 	case sdk.AzureBlobFilesystemProvider:
 
- 		config, err := getAzureConfig(r)
 
- 		if err != nil {
 
- 			return fs, err
 
- 		}
 
- 		fs.AzBlobConfig = config
 
- 	case sdk.GCSFilesystemProvider:
 
- 		config, err := getGCSConfig(r)
 
- 		if err != nil {
 
- 			return fs, err
 
- 		}
 
- 		fs.GCSConfig = config
 
- 	case sdk.CryptedFilesystemProvider:
 
- 		fs.CryptConfig.Passphrase = getSecretFromFormField(r, "crypt_passphrase")
 
- 		fs.CryptConfig.OSFsConfig = getOsConfigFromPostFields(r, "cryptfs_read_buffer_size", "cryptfs_write_buffer_size")
 
- 	case sdk.SFTPFilesystemProvider:
 
- 		config, err := getSFTPConfig(r)
 
- 		if err != nil {
 
- 			return fs, err
 
- 		}
 
- 		fs.SFTPConfig = config
 
- 	case sdk.HTTPFilesystemProvider:
 
- 		fs.HTTPConfig = getHTTPFsConfig(r)
 
- 	}
 
- 	return fs, nil
 
- }
 
- func getAdminHiddenUserPageSections(r *http.Request) int {
 
- 	var result int
 
- 	for _, val := range r.Form["user_page_hidden_sections"] {
 
- 		switch val {
 
- 		case "1":
 
- 			result++
 
- 		case "2":
 
- 			result += 2
 
- 		case "3":
 
- 			result += 4
 
- 		case "4":
 
- 			result += 8
 
- 		case "5":
 
- 			result += 16
 
- 		case "6":
 
- 			result += 32
 
- 		case "7":
 
- 			result += 64
 
- 		}
 
- 	}
 
- 	return result
 
- }
 
- func getAdminFromPostFields(r *http.Request) (dataprovider.Admin, error) {
 
- 	var admin dataprovider.Admin
 
- 	err := r.ParseForm()
 
- 	if err != nil {
 
- 		return admin, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	status, err := strconv.Atoi(r.Form.Get("status"))
 
- 	if err != nil {
 
- 		return admin, fmt.Errorf("invalid status: %w", err)
 
- 	}
 
- 	admin.Username = strings.TrimSpace(r.Form.Get("username"))
 
- 	admin.Password = strings.TrimSpace(r.Form.Get("password"))
 
- 	admin.Permissions = r.Form["permissions"]
 
- 	admin.Email = strings.TrimSpace(r.Form.Get("email"))
 
- 	admin.Status = status
 
- 	admin.Role = strings.TrimSpace(r.Form.Get("role"))
 
- 	admin.Filters.AllowList = getSliceFromDelimitedValues(r.Form.Get("allowed_ip"), ",")
 
- 	admin.Filters.AllowAPIKeyAuth = r.Form.Get("allow_api_key_auth") != ""
 
- 	admin.Filters.RequireTwoFactor = r.Form.Get("require_two_factor") != ""
 
- 	admin.Filters.RequirePasswordChange = r.Form.Get("require_password_change") != ""
 
- 	admin.AdditionalInfo = r.Form.Get("additional_info")
 
- 	admin.Description = r.Form.Get("description")
 
- 	admin.Filters.Preferences.HideUserPageSections = getAdminHiddenUserPageSections(r)
 
- 	admin.Filters.Preferences.DefaultUsersExpiration = 0
 
- 	if val := r.Form.Get("default_users_expiration"); val != "" {
 
- 		defaultUsersExpiration, err := strconv.Atoi(r.Form.Get("default_users_expiration"))
 
- 		if err != nil {
 
- 			return admin, fmt.Errorf("invalid default users expiration: %w", err)
 
- 		}
 
- 		admin.Filters.Preferences.DefaultUsersExpiration = defaultUsersExpiration
 
- 	}
 
- 	for k := range r.Form {
 
- 		if hasPrefixAndSuffix(k, "groups[", "][group]") {
 
- 			groupName := strings.TrimSpace(r.Form.Get(k))
 
- 			if groupName != "" {
 
- 				group := dataprovider.AdminGroupMapping{
 
- 					Name: groupName,
 
- 				}
 
- 				base, _ := strings.CutSuffix(k, "[group]")
 
- 				addAsGroupType := strings.TrimSpace(r.Form.Get(base + "[group_type]"))
 
- 				switch addAsGroupType {
 
- 				case "1":
 
- 					group.Options.AddToUsersAs = dataprovider.GroupAddToUsersAsPrimary
 
- 				case "2":
 
- 					group.Options.AddToUsersAs = dataprovider.GroupAddToUsersAsSecondary
 
- 				default:
 
- 					group.Options.AddToUsersAs = dataprovider.GroupAddToUsersAsMembership
 
- 				}
 
- 				admin.Groups = append(admin.Groups, group)
 
- 			}
 
- 		}
 
- 	}
 
- 	return admin, nil
 
- }
 
- func replacePlaceholders(field string, replacements map[string]string) string {
 
- 	for k, v := range replacements {
 
- 		field = strings.ReplaceAll(field, k, v)
 
- 	}
 
- 	return field
 
- }
 
- func getFolderFromTemplate(folder vfs.BaseVirtualFolder, name string) vfs.BaseVirtualFolder {
 
- 	folder.Name = name
 
- 	replacements := make(map[string]string)
 
- 	replacements["%name%"] = folder.Name
 
- 	folder.MappedPath = replacePlaceholders(folder.MappedPath, replacements)
 
- 	folder.Description = replacePlaceholders(folder.Description, replacements)
 
- 	switch folder.FsConfig.Provider {
 
- 	case sdk.CryptedFilesystemProvider:
 
- 		folder.FsConfig.CryptConfig = getCryptFsFromTemplate(folder.FsConfig.CryptConfig, replacements)
 
- 	case sdk.S3FilesystemProvider:
 
- 		folder.FsConfig.S3Config = getS3FsFromTemplate(folder.FsConfig.S3Config, replacements)
 
- 	case sdk.GCSFilesystemProvider:
 
- 		folder.FsConfig.GCSConfig = getGCSFsFromTemplate(folder.FsConfig.GCSConfig, replacements)
 
- 	case sdk.AzureBlobFilesystemProvider:
 
- 		folder.FsConfig.AzBlobConfig = getAzBlobFsFromTemplate(folder.FsConfig.AzBlobConfig, replacements)
 
- 	case sdk.SFTPFilesystemProvider:
 
- 		folder.FsConfig.SFTPConfig = getSFTPFsFromTemplate(folder.FsConfig.SFTPConfig, replacements)
 
- 	case sdk.HTTPFilesystemProvider:
 
- 		folder.FsConfig.HTTPConfig = getHTTPFsFromTemplate(folder.FsConfig.HTTPConfig, replacements)
 
- 	}
 
- 	return folder
 
- }
 
- func getCryptFsFromTemplate(fsConfig vfs.CryptFsConfig, replacements map[string]string) vfs.CryptFsConfig {
 
- 	if fsConfig.Passphrase != nil {
 
- 		if fsConfig.Passphrase.IsPlain() {
 
- 			payload := replacePlaceholders(fsConfig.Passphrase.GetPayload(), replacements)
 
- 			fsConfig.Passphrase = kms.NewPlainSecret(payload)
 
- 		}
 
- 	}
 
- 	return fsConfig
 
- }
 
- func getS3FsFromTemplate(fsConfig vfs.S3FsConfig, replacements map[string]string) vfs.S3FsConfig {
 
- 	fsConfig.KeyPrefix = replacePlaceholders(fsConfig.KeyPrefix, replacements)
 
- 	fsConfig.AccessKey = replacePlaceholders(fsConfig.AccessKey, replacements)
 
- 	if fsConfig.AccessSecret != nil && fsConfig.AccessSecret.IsPlain() {
 
- 		payload := replacePlaceholders(fsConfig.AccessSecret.GetPayload(), replacements)
 
- 		fsConfig.AccessSecret = kms.NewPlainSecret(payload)
 
- 	}
 
- 	if fsConfig.SSECustomerKey != nil && fsConfig.SSECustomerKey.IsPlain() {
 
- 		payload := replacePlaceholders(fsConfig.SSECustomerKey.GetPayload(), replacements)
 
- 		fsConfig.SSECustomerKey = kms.NewPlainSecret(payload)
 
- 	}
 
- 	return fsConfig
 
- }
 
- func getGCSFsFromTemplate(fsConfig vfs.GCSFsConfig, replacements map[string]string) vfs.GCSFsConfig {
 
- 	fsConfig.KeyPrefix = replacePlaceholders(fsConfig.KeyPrefix, replacements)
 
- 	return fsConfig
 
- }
 
- func getAzBlobFsFromTemplate(fsConfig vfs.AzBlobFsConfig, replacements map[string]string) vfs.AzBlobFsConfig {
 
- 	fsConfig.KeyPrefix = replacePlaceholders(fsConfig.KeyPrefix, replacements)
 
- 	fsConfig.AccountName = replacePlaceholders(fsConfig.AccountName, replacements)
 
- 	if fsConfig.AccountKey != nil && fsConfig.AccountKey.IsPlain() {
 
- 		payload := replacePlaceholders(fsConfig.AccountKey.GetPayload(), replacements)
 
- 		fsConfig.AccountKey = kms.NewPlainSecret(payload)
 
- 	}
 
- 	return fsConfig
 
- }
 
- func getSFTPFsFromTemplate(fsConfig vfs.SFTPFsConfig, replacements map[string]string) vfs.SFTPFsConfig {
 
- 	fsConfig.Prefix = replacePlaceholders(fsConfig.Prefix, replacements)
 
- 	fsConfig.Username = replacePlaceholders(fsConfig.Username, replacements)
 
- 	if fsConfig.Password != nil && fsConfig.Password.IsPlain() {
 
- 		payload := replacePlaceholders(fsConfig.Password.GetPayload(), replacements)
 
- 		fsConfig.Password = kms.NewPlainSecret(payload)
 
- 	}
 
- 	return fsConfig
 
- }
 
- func getHTTPFsFromTemplate(fsConfig vfs.HTTPFsConfig, replacements map[string]string) vfs.HTTPFsConfig {
 
- 	fsConfig.Username = replacePlaceholders(fsConfig.Username, replacements)
 
- 	return fsConfig
 
- }
 
- func getUserFromTemplate(user dataprovider.User, template userTemplateFields) dataprovider.User {
 
- 	user.Username = template.Username
 
- 	user.Password = template.Password
 
- 	user.PublicKeys = template.PublicKeys
 
- 	replacements := make(map[string]string)
 
- 	replacements["%username%"] = user.Username
 
- 	if user.Password != "" && !user.IsPasswordHashed() {
 
- 		user.Password = replacePlaceholders(user.Password, replacements)
 
- 		replacements["%password%"] = user.Password
 
- 	}
 
- 	user.HomeDir = replacePlaceholders(user.HomeDir, replacements)
 
- 	var vfolders []vfs.VirtualFolder
 
- 	for _, vfolder := range user.VirtualFolders {
 
- 		vfolder.Name = replacePlaceholders(vfolder.Name, replacements)
 
- 		vfolder.VirtualPath = replacePlaceholders(vfolder.VirtualPath, replacements)
 
- 		vfolders = append(vfolders, vfolder)
 
- 	}
 
- 	user.VirtualFolders = vfolders
 
- 	user.Description = replacePlaceholders(user.Description, replacements)
 
- 	user.AdditionalInfo = replacePlaceholders(user.AdditionalInfo, replacements)
 
- 	user.Filters.StartDirectory = replacePlaceholders(user.Filters.StartDirectory, replacements)
 
- 	switch user.FsConfig.Provider {
 
- 	case sdk.CryptedFilesystemProvider:
 
- 		user.FsConfig.CryptConfig = getCryptFsFromTemplate(user.FsConfig.CryptConfig, replacements)
 
- 	case sdk.S3FilesystemProvider:
 
- 		user.FsConfig.S3Config = getS3FsFromTemplate(user.FsConfig.S3Config, replacements)
 
- 	case sdk.GCSFilesystemProvider:
 
- 		user.FsConfig.GCSConfig = getGCSFsFromTemplate(user.FsConfig.GCSConfig, replacements)
 
- 	case sdk.AzureBlobFilesystemProvider:
 
- 		user.FsConfig.AzBlobConfig = getAzBlobFsFromTemplate(user.FsConfig.AzBlobConfig, replacements)
 
- 	case sdk.SFTPFilesystemProvider:
 
- 		user.FsConfig.SFTPConfig = getSFTPFsFromTemplate(user.FsConfig.SFTPConfig, replacements)
 
- 	case sdk.HTTPFilesystemProvider:
 
- 		user.FsConfig.HTTPConfig = getHTTPFsFromTemplate(user.FsConfig.HTTPConfig, replacements)
 
- 	}
 
- 	return user
 
- }
 
- func getTransferLimits(r *http.Request) (int64, int64, int64, error) {
 
- 	dataTransferUL, err := strconv.ParseInt(r.Form.Get("upload_data_transfer"), 10, 64)
 
- 	if err != nil {
 
- 		return 0, 0, 0, fmt.Errorf("invalid upload data transfer: %w", err)
 
- 	}
 
- 	dataTransferDL, err := strconv.ParseInt(r.Form.Get("download_data_transfer"), 10, 64)
 
- 	if err != nil {
 
- 		return 0, 0, 0, fmt.Errorf("invalid download data transfer: %w", err)
 
- 	}
 
- 	dataTransferTotal, err := strconv.ParseInt(r.Form.Get("total_data_transfer"), 10, 64)
 
- 	if err != nil {
 
- 		return 0, 0, 0, fmt.Errorf("invalid total data transfer: %w", err)
 
- 	}
 
- 	return dataTransferUL, dataTransferDL, dataTransferTotal, nil
 
- }
 
- func getQuotaLimits(r *http.Request) (int64, int, error) {
 
- 	quotaSize, err := util.ParseBytes(r.Form.Get("quota_size"))
 
- 	if err != nil {
 
- 		return 0, 0, util.NewI18nError(fmt.Errorf("invalid quota size: %w", err), util.I18nErrorInvalidQuotaSize)
 
- 	}
 
- 	quotaFiles, err := strconv.Atoi(r.Form.Get("quota_files"))
 
- 	if err != nil {
 
- 		return 0, 0, fmt.Errorf("invalid quota files: %w", err)
 
- 	}
 
- 	return quotaSize, quotaFiles, nil
 
- }
 
- func updateRepeaterFormFields(r *http.Request) {
 
- 	for k := range r.Form {
 
- 		if hasPrefixAndSuffix(k, "public_keys[", "][public_key]") {
 
- 			key := r.Form.Get(k)
 
- 			if strings.TrimSpace(key) != "" {
 
- 				r.Form.Add("public_keys", key)
 
- 			}
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "tls_certs[", "][tls_cert]") {
 
- 			cert := strings.TrimSpace(r.Form.Get(k))
 
- 			if cert != "" {
 
- 				r.Form.Add("tls_certs", cert)
 
- 			}
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "virtual_folders[", "][vfolder_path]") {
 
- 			base, _ := strings.CutSuffix(k, "[vfolder_path]")
 
- 			r.Form.Add("vfolder_path", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("vfolder_name", strings.TrimSpace(r.Form.Get(base+"[vfolder_name]")))
 
- 			r.Form.Add("vfolder_quota_files", strings.TrimSpace(r.Form.Get(base+"[vfolder_quota_files]")))
 
- 			r.Form.Add("vfolder_quota_size", strings.TrimSpace(r.Form.Get(base+"[vfolder_quota_size]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "directory_permissions[", "][sub_perm_path]") {
 
- 			base, _ := strings.CutSuffix(k, "[sub_perm_path]")
 
- 			r.Form.Add("sub_perm_path", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form["sub_perm_permissions"+strconv.Itoa(len(r.Form["sub_perm_path"])-1)] = r.Form[base+"[sub_perm_permissions][]"]
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "directory_patterns[", "][pattern_path]") {
 
- 			base, _ := strings.CutSuffix(k, "[pattern_path]")
 
- 			r.Form.Add("pattern_path", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("patterns", strings.TrimSpace(r.Form.Get(base+"[patterns]")))
 
- 			r.Form.Add("pattern_type", strings.TrimSpace(r.Form.Get(base+"[pattern_type]")))
 
- 			r.Form.Add("pattern_policy", strings.TrimSpace(r.Form.Get(base+"[pattern_policy]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "access_time_restrictions[", "][access_time_day_of_week]") {
 
- 			base, _ := strings.CutSuffix(k, "[access_time_day_of_week]")
 
- 			r.Form.Add("access_time_day_of_week", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("access_time_start", strings.TrimSpace(r.Form.Get(base+"[access_time_start]")))
 
- 			r.Form.Add("access_time_end", strings.TrimSpace(r.Form.Get(base+"[access_time_end]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "src_bandwidth_limits[", "][bandwidth_limit_sources]") {
 
- 			base, _ := strings.CutSuffix(k, "[bandwidth_limit_sources]")
 
- 			r.Form.Add("bandwidth_limit_sources", r.Form.Get(k))
 
- 			r.Form.Add("upload_bandwidth_source", strings.TrimSpace(r.Form.Get(base+"[upload_bandwidth_source]")))
 
- 			r.Form.Add("download_bandwidth_source", strings.TrimSpace(r.Form.Get(base+"[download_bandwidth_source]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "template_users[", "][tpl_username]") {
 
- 			base, _ := strings.CutSuffix(k, "[tpl_username]")
 
- 			r.Form.Add("tpl_username", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("tpl_password", strings.TrimSpace(r.Form.Get(base+"[tpl_password]")))
 
- 			r.Form.Add("tpl_public_keys", strings.TrimSpace(r.Form.Get(base+"[tpl_public_keys]")))
 
- 			continue
 
- 		}
 
- 	}
 
- }
 
- func getUserFromPostFields(r *http.Request) (dataprovider.User, error) {
 
- 	user := dataprovider.User{}
 
- 	err := r.ParseMultipartForm(maxRequestSize)
 
- 	if err != nil {
 
- 		return user, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	defer r.MultipartForm.RemoveAll() //nolint:errcheck
 
- 	updateRepeaterFormFields(r)
 
- 	uid, err := strconv.Atoi(r.Form.Get("uid"))
 
- 	if err != nil {
 
- 		return user, fmt.Errorf("invalid uid: %w", err)
 
- 	}
 
- 	gid, err := strconv.Atoi(r.Form.Get("gid"))
 
- 	if err != nil {
 
- 		return user, fmt.Errorf("invalid uid: %w", err)
 
- 	}
 
- 	maxSessions, err := strconv.Atoi(r.Form.Get("max_sessions"))
 
- 	if err != nil {
 
- 		return user, fmt.Errorf("invalid max sessions: %w", err)
 
- 	}
 
- 	quotaSize, quotaFiles, err := getQuotaLimits(r)
 
- 	if err != nil {
 
- 		return user, err
 
- 	}
 
- 	bandwidthUL, err := strconv.ParseInt(r.Form.Get("upload_bandwidth"), 10, 64)
 
- 	if err != nil {
 
- 		return user, fmt.Errorf("invalid upload bandwidth: %w", err)
 
- 	}
 
- 	bandwidthDL, err := strconv.ParseInt(r.Form.Get("download_bandwidth"), 10, 64)
 
- 	if err != nil {
 
- 		return user, fmt.Errorf("invalid download bandwidth: %w", err)
 
- 	}
 
- 	dataTransferUL, dataTransferDL, dataTransferTotal, err := getTransferLimits(r)
 
- 	if err != nil {
 
- 		return user, err
 
- 	}
 
- 	status, err := strconv.Atoi(r.Form.Get("status"))
 
- 	if err != nil {
 
- 		return user, fmt.Errorf("invalid status: %w", err)
 
- 	}
 
- 	expirationDateMillis := int64(0)
 
- 	expirationDateString := r.Form.Get("expiration_date")
 
- 	if strings.TrimSpace(expirationDateString) != "" {
 
- 		expirationDate, err := time.Parse(webDateTimeFormat, expirationDateString)
 
- 		if err != nil {
 
- 			return user, err
 
- 		}
 
- 		expirationDateMillis = util.GetTimeAsMsSinceEpoch(expirationDate)
 
- 	}
 
- 	fsConfig, err := getFsConfigFromPostFields(r)
 
- 	if err != nil {
 
- 		return user, err
 
- 	}
 
- 	filters, err := getFiltersFromUserPostFields(r)
 
- 	if err != nil {
 
- 		return user, err
 
- 	}
 
- 	filters.TLSCerts = r.Form["tls_certs"]
 
- 	user = dataprovider.User{
 
- 		BaseUser: sdk.BaseUser{
 
- 			Username:             strings.TrimSpace(r.Form.Get("username")),
 
- 			Email:                strings.TrimSpace(r.Form.Get("email")),
 
- 			Password:             strings.TrimSpace(r.Form.Get("password")),
 
- 			PublicKeys:           r.Form["public_keys"],
 
- 			HomeDir:              strings.TrimSpace(r.Form.Get("home_dir")),
 
- 			UID:                  uid,
 
- 			GID:                  gid,
 
- 			Permissions:          getUserPermissionsFromPostFields(r),
 
- 			MaxSessions:          maxSessions,
 
- 			QuotaSize:            quotaSize,
 
- 			QuotaFiles:           quotaFiles,
 
- 			UploadBandwidth:      bandwidthUL,
 
- 			DownloadBandwidth:    bandwidthDL,
 
- 			UploadDataTransfer:   dataTransferUL,
 
- 			DownloadDataTransfer: dataTransferDL,
 
- 			TotalDataTransfer:    dataTransferTotal,
 
- 			Status:               status,
 
- 			ExpirationDate:       expirationDateMillis,
 
- 			AdditionalInfo:       r.Form.Get("additional_info"),
 
- 			Description:          r.Form.Get("description"),
 
- 			Role:                 strings.TrimSpace(r.Form.Get("role")),
 
- 		},
 
- 		Filters: dataprovider.UserFilters{
 
- 			BaseUserFilters:       filters,
 
- 			RequirePasswordChange: r.Form.Get("require_password_change") != "",
 
- 		},
 
- 		VirtualFolders: getVirtualFoldersFromPostFields(r),
 
- 		FsConfig:       fsConfig,
 
- 		Groups:         getGroupsFromUserPostFields(r),
 
- 	}
 
- 	return user, nil
 
- }
 
- func getGroupFromPostFields(r *http.Request) (dataprovider.Group, error) {
 
- 	group := dataprovider.Group{}
 
- 	err := r.ParseMultipartForm(maxRequestSize)
 
- 	if err != nil {
 
- 		return group, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	defer r.MultipartForm.RemoveAll() //nolint:errcheck
 
- 	updateRepeaterFormFields(r)
 
- 	maxSessions, err := strconv.Atoi(r.Form.Get("max_sessions"))
 
- 	if err != nil {
 
- 		return group, fmt.Errorf("invalid max sessions: %w", err)
 
- 	}
 
- 	quotaSize, quotaFiles, err := getQuotaLimits(r)
 
- 	if err != nil {
 
- 		return group, err
 
- 	}
 
- 	bandwidthUL, err := strconv.ParseInt(r.Form.Get("upload_bandwidth"), 10, 64)
 
- 	if err != nil {
 
- 		return group, fmt.Errorf("invalid upload bandwidth: %w", err)
 
- 	}
 
- 	bandwidthDL, err := strconv.ParseInt(r.Form.Get("download_bandwidth"), 10, 64)
 
- 	if err != nil {
 
- 		return group, fmt.Errorf("invalid download bandwidth: %w", err)
 
- 	}
 
- 	dataTransferUL, dataTransferDL, dataTransferTotal, err := getTransferLimits(r)
 
- 	if err != nil {
 
- 		return group, err
 
- 	}
 
- 	expiresIn, err := strconv.Atoi(r.Form.Get("expires_in"))
 
- 	if err != nil {
 
- 		return group, fmt.Errorf("invalid expires in: %w", err)
 
- 	}
 
- 	fsConfig, err := getFsConfigFromPostFields(r)
 
- 	if err != nil {
 
- 		return group, err
 
- 	}
 
- 	filters, err := getFiltersFromUserPostFields(r)
 
- 	if err != nil {
 
- 		return group, err
 
- 	}
 
- 	group = dataprovider.Group{
 
- 		BaseGroup: sdk.BaseGroup{
 
- 			Name:        strings.TrimSpace(r.Form.Get("name")),
 
- 			Description: r.Form.Get("description"),
 
- 		},
 
- 		UserSettings: dataprovider.GroupUserSettings{
 
- 			BaseGroupUserSettings: sdk.BaseGroupUserSettings{
 
- 				HomeDir:              strings.TrimSpace(r.Form.Get("home_dir")),
 
- 				MaxSessions:          maxSessions,
 
- 				QuotaSize:            quotaSize,
 
- 				QuotaFiles:           quotaFiles,
 
- 				Permissions:          getSubDirPermissionsFromPostFields(r),
 
- 				UploadBandwidth:      bandwidthUL,
 
- 				DownloadBandwidth:    bandwidthDL,
 
- 				UploadDataTransfer:   dataTransferUL,
 
- 				DownloadDataTransfer: dataTransferDL,
 
- 				TotalDataTransfer:    dataTransferTotal,
 
- 				ExpiresIn:            expiresIn,
 
- 				Filters:              filters,
 
- 			},
 
- 			FsConfig: fsConfig,
 
- 		},
 
- 		VirtualFolders: getVirtualFoldersFromPostFields(r),
 
- 	}
 
- 	return group, nil
 
- }
 
- func getKeyValsFromPostFields(r *http.Request, key, val string) []dataprovider.KeyValue {
 
- 	var res []dataprovider.KeyValue
 
- 	keys := r.Form[key]
 
- 	values := r.Form[val]
 
- 	for idx, k := range keys {
 
- 		v := values[idx]
 
- 		if k != "" && v != "" {
 
- 			res = append(res, dataprovider.KeyValue{
 
- 				Key:   k,
 
- 				Value: v,
 
- 			})
 
- 		}
 
- 	}
 
- 	return res
 
- }
 
- func getRenameConfigsFromPostFields(r *http.Request) []dataprovider.RenameConfig {
 
- 	var res []dataprovider.RenameConfig
 
- 	keys := r.Form["fs_rename_source"]
 
- 	values := r.Form["fs_rename_target"]
 
- 	for idx, k := range keys {
 
- 		v := values[idx]
 
- 		if k != "" && v != "" {
 
- 			opts := r.Form["fs_rename_options"+strconv.Itoa(idx)]
 
- 			res = append(res, dataprovider.RenameConfig{
 
- 				KeyValue: dataprovider.KeyValue{
 
- 					Key:   k,
 
- 					Value: v,
 
- 				},
 
- 				UpdateModTime: slices.Contains(opts, "1"),
 
- 			})
 
- 		}
 
- 	}
 
- 	return res
 
- }
 
- func getFoldersRetentionFromPostFields(r *http.Request) ([]dataprovider.FolderRetention, error) {
 
- 	var res []dataprovider.FolderRetention
 
- 	paths := r.Form["folder_retention_path"]
 
- 	values := r.Form["folder_retention_val"]
 
- 	for idx, p := range paths {
 
- 		if p != "" {
 
- 			retention, err := strconv.Atoi(values[idx])
 
- 			if err != nil {
 
- 				return nil, fmt.Errorf("invalid retention for path %q: %w", p, err)
 
- 			}
 
- 			opts := r.Form["folder_retention_options"+strconv.Itoa(idx)]
 
- 			res = append(res, dataprovider.FolderRetention{
 
- 				Path:            p,
 
- 				Retention:       retention,
 
- 				DeleteEmptyDirs: slices.Contains(opts, "1"),
 
- 			})
 
- 		}
 
- 	}
 
- 	return res, nil
 
- }
 
- func getHTTPPartsFromPostFields(r *http.Request) []dataprovider.HTTPPart {
 
- 	var result []dataprovider.HTTPPart
 
- 	names := r.Form["http_part_name"]
 
- 	files := r.Form["http_part_file"]
 
- 	headers := r.Form["http_part_headers"]
 
- 	bodies := r.Form["http_part_body"]
 
- 	orders := r.Form["http_part_order"]
 
- 	for idx, partName := range names {
 
- 		if partName != "" {
 
- 			order, err := strconv.Atoi(orders[idx])
 
- 			if err == nil {
 
- 				filePath := files[idx]
 
- 				body := bodies[idx]
 
- 				concatHeaders := getSliceFromDelimitedValues(headers[idx], "\n")
 
- 				var headers []dataprovider.KeyValue
 
- 				for _, h := range concatHeaders {
 
- 					values := strings.SplitN(h, ":", 2)
 
- 					if len(values) > 1 {
 
- 						headers = append(headers, dataprovider.KeyValue{
 
- 							Key:   strings.TrimSpace(values[0]),
 
- 							Value: strings.TrimSpace(values[1]),
 
- 						})
 
- 					}
 
- 				}
 
- 				result = append(result, dataprovider.HTTPPart{
 
- 					Name:     partName,
 
- 					Filepath: filePath,
 
- 					Headers:  headers,
 
- 					Body:     body,
 
- 					Order:    order,
 
- 				})
 
- 			}
 
- 		}
 
- 	}
 
- 	sort.Slice(result, func(i, j int) bool {
 
- 		return result[i].Order < result[j].Order
 
- 	})
 
- 	return result
 
- }
 
- func updateRepeaterFormActionFields(r *http.Request) {
 
- 	for k := range r.Form {
 
- 		if hasPrefixAndSuffix(k, "http_headers[", "][http_header_key]") {
 
- 			base, _ := strings.CutSuffix(k, "[http_header_key]")
 
- 			r.Form.Add("http_header_key", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("http_header_value", strings.TrimSpace(r.Form.Get(base+"[http_header_value]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "query_parameters[", "][http_query_key]") {
 
- 			base, _ := strings.CutSuffix(k, "[http_query_key]")
 
- 			r.Form.Add("http_query_key", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("http_query_value", strings.TrimSpace(r.Form.Get(base+"[http_query_value]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "multipart_body[", "][http_part_name]") {
 
- 			base, _ := strings.CutSuffix(k, "[http_part_name]")
 
- 			order, _ := strings.CutPrefix(k, "multipart_body[")
 
- 			order, _ = strings.CutSuffix(order, "][http_part_name]")
 
- 			r.Form.Add("http_part_name", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("http_part_file", strings.TrimSpace(r.Form.Get(base+"[http_part_file]")))
 
- 			r.Form.Add("http_part_headers", strings.TrimSpace(r.Form.Get(base+"[http_part_headers]")))
 
- 			r.Form.Add("http_part_body", strings.TrimSpace(r.Form.Get(base+"[http_part_body]")))
 
- 			r.Form.Add("http_part_order", order)
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "env_vars[", "][cmd_env_key]") {
 
- 			base, _ := strings.CutSuffix(k, "[cmd_env_key]")
 
- 			r.Form.Add("cmd_env_key", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("cmd_env_value", strings.TrimSpace(r.Form.Get(base+"[cmd_env_value]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "data_retention[", "][folder_retention_path]") {
 
- 			base, _ := strings.CutSuffix(k, "[folder_retention_path]")
 
- 			r.Form.Add("folder_retention_path", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("folder_retention_val", strings.TrimSpace(r.Form.Get(base+"[folder_retention_val]")))
 
- 			r.Form["folder_retention_options"+strconv.Itoa(len(r.Form["folder_retention_path"])-1)] =
 
- 				r.Form[base+"[folder_retention_options][]"]
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "fs_rename[", "][fs_rename_source]") {
 
- 			base, _ := strings.CutSuffix(k, "[fs_rename_source]")
 
- 			r.Form.Add("fs_rename_source", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("fs_rename_target", strings.TrimSpace(r.Form.Get(base+"[fs_rename_target]")))
 
- 			r.Form["fs_rename_options"+strconv.Itoa(len(r.Form["fs_rename_source"])-1)] =
 
- 				r.Form[base+"[fs_rename_options][]"]
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "fs_copy[", "][fs_copy_source]") {
 
- 			base, _ := strings.CutSuffix(k, "[fs_copy_source]")
 
- 			r.Form.Add("fs_copy_source", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("fs_copy_target", strings.TrimSpace(r.Form.Get(base+"[fs_copy_target]")))
 
- 			continue
 
- 		}
 
- 	}
 
- }
 
- func getEventActionOptionsFromPostFields(r *http.Request) (dataprovider.BaseEventActionOptions, error) {
 
- 	updateRepeaterFormActionFields(r)
 
- 	httpTimeout, err := strconv.Atoi(r.Form.Get("http_timeout"))
 
- 	if err != nil {
 
- 		return dataprovider.BaseEventActionOptions{}, fmt.Errorf("invalid http timeout: %w", err)
 
- 	}
 
- 	cmdTimeout, err := strconv.Atoi(r.Form.Get("cmd_timeout"))
 
- 	if err != nil {
 
- 		return dataprovider.BaseEventActionOptions{}, fmt.Errorf("invalid command timeout: %w", err)
 
- 	}
 
- 	foldersRetention, err := getFoldersRetentionFromPostFields(r)
 
- 	if err != nil {
 
- 		return dataprovider.BaseEventActionOptions{}, err
 
- 	}
 
- 	fsActionType, err := strconv.Atoi(r.Form.Get("fs_action_type"))
 
- 	if err != nil {
 
- 		return dataprovider.BaseEventActionOptions{}, fmt.Errorf("invalid fs action type: %w", err)
 
- 	}
 
- 	pwdExpirationThreshold, err := strconv.Atoi(r.Form.Get("pwd_expiration_threshold"))
 
- 	if err != nil {
 
- 		return dataprovider.BaseEventActionOptions{}, fmt.Errorf("invalid password expiration threshold: %w", err)
 
- 	}
 
- 	var disableThreshold, deleteThreshold int
 
- 	if val, err := strconv.Atoi(r.Form.Get("inactivity_disable_threshold")); err == nil {
 
- 		disableThreshold = val
 
- 	}
 
- 	if val, err := strconv.Atoi(r.Form.Get("inactivity_delete_threshold")); err == nil {
 
- 		deleteThreshold = val
 
- 	}
 
- 	var emailAttachments []string
 
- 	if r.Form.Get("email_attachments") != "" {
 
- 		emailAttachments = getSliceFromDelimitedValues(r.Form.Get("email_attachments"), ",")
 
- 	}
 
- 	var cmdArgs []string
 
- 	if r.Form.Get("cmd_arguments") != "" {
 
- 		cmdArgs = getSliceFromDelimitedValues(r.Form.Get("cmd_arguments"), ",")
 
- 	}
 
- 	idpMode := 0
 
- 	if r.Form.Get("idp_mode") == "1" {
 
- 		idpMode = 1
 
- 	}
 
- 	emailContentType := 0
 
- 	if r.Form.Get("email_content_type") == "1" {
 
- 		emailContentType = 1
 
- 	}
 
- 	options := dataprovider.BaseEventActionOptions{
 
- 		HTTPConfig: dataprovider.EventActionHTTPConfig{
 
- 			Endpoint:        strings.TrimSpace(r.Form.Get("http_endpoint")),
 
- 			Username:        strings.TrimSpace(r.Form.Get("http_username")),
 
- 			Password:        getSecretFromFormField(r, "http_password"),
 
- 			Headers:         getKeyValsFromPostFields(r, "http_header_key", "http_header_value"),
 
- 			Timeout:         httpTimeout,
 
- 			SkipTLSVerify:   r.Form.Get("http_skip_tls_verify") != "",
 
- 			Method:          r.Form.Get("http_method"),
 
- 			QueryParameters: getKeyValsFromPostFields(r, "http_query_key", "http_query_value"),
 
- 			Body:            r.Form.Get("http_body"),
 
- 			Parts:           getHTTPPartsFromPostFields(r),
 
- 		},
 
- 		CmdConfig: dataprovider.EventActionCommandConfig{
 
- 			Cmd:     strings.TrimSpace(r.Form.Get("cmd_path")),
 
- 			Args:    cmdArgs,
 
- 			Timeout: cmdTimeout,
 
- 			EnvVars: getKeyValsFromPostFields(r, "cmd_env_key", "cmd_env_value"),
 
- 		},
 
- 		EmailConfig: dataprovider.EventActionEmailConfig{
 
- 			Recipients:  getSliceFromDelimitedValues(r.Form.Get("email_recipients"), ","),
 
- 			Bcc:         getSliceFromDelimitedValues(r.Form.Get("email_bcc"), ","),
 
- 			Subject:     r.Form.Get("email_subject"),
 
- 			ContentType: emailContentType,
 
- 			Body:        r.Form.Get("email_body"),
 
- 			Attachments: emailAttachments,
 
- 		},
 
- 		RetentionConfig: dataprovider.EventActionDataRetentionConfig{
 
- 			Folders: foldersRetention,
 
- 		},
 
- 		FsConfig: dataprovider.EventActionFilesystemConfig{
 
- 			Type:    fsActionType,
 
- 			Renames: getRenameConfigsFromPostFields(r),
 
- 			Deletes: getSliceFromDelimitedValues(r.Form.Get("fs_delete_paths"), ","),
 
- 			MkDirs:  getSliceFromDelimitedValues(r.Form.Get("fs_mkdir_paths"), ","),
 
- 			Exist:   getSliceFromDelimitedValues(r.Form.Get("fs_exist_paths"), ","),
 
- 			Copy:    getKeyValsFromPostFields(r, "fs_copy_source", "fs_copy_target"),
 
- 			Compress: dataprovider.EventActionFsCompress{
 
- 				Name:  strings.TrimSpace(r.Form.Get("fs_compress_name")),
 
- 				Paths: getSliceFromDelimitedValues(r.Form.Get("fs_compress_paths"), ","),
 
- 			},
 
- 		},
 
- 		PwdExpirationConfig: dataprovider.EventActionPasswordExpiration{
 
- 			Threshold: pwdExpirationThreshold,
 
- 		},
 
- 		UserInactivityConfig: dataprovider.EventActionUserInactivity{
 
- 			DisableThreshold: disableThreshold,
 
- 			DeleteThreshold:  deleteThreshold,
 
- 		},
 
- 		IDPConfig: dataprovider.EventActionIDPAccountCheck{
 
- 			Mode:          idpMode,
 
- 			TemplateUser:  strings.TrimSpace(r.Form.Get("idp_user")),
 
- 			TemplateAdmin: strings.TrimSpace(r.Form.Get("idp_admin")),
 
- 		},
 
- 	}
 
- 	return options, nil
 
- }
 
- func getEventActionFromPostFields(r *http.Request) (dataprovider.BaseEventAction, error) {
 
- 	err := r.ParseForm()
 
- 	if err != nil {
 
- 		return dataprovider.BaseEventAction{}, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	actionType, err := strconv.Atoi(r.Form.Get("type"))
 
- 	if err != nil {
 
- 		return dataprovider.BaseEventAction{}, fmt.Errorf("invalid action type: %w", err)
 
- 	}
 
- 	options, err := getEventActionOptionsFromPostFields(r)
 
- 	if err != nil {
 
- 		return dataprovider.BaseEventAction{}, err
 
- 	}
 
- 	action := dataprovider.BaseEventAction{
 
- 		Name:        strings.TrimSpace(r.Form.Get("name")),
 
- 		Description: r.Form.Get("description"),
 
- 		Type:        actionType,
 
- 		Options:     options,
 
- 	}
 
- 	return action, nil
 
- }
 
- func getIDPLoginEventFromPostField(r *http.Request) int {
 
- 	switch r.Form.Get("idp_login_event") {
 
- 	case "1":
 
- 		return 1
 
- 	case "2":
 
- 		return 2
 
- 	default:
 
- 		return 0
 
- 	}
 
- }
 
- func getEventRuleConditionsFromPostFields(r *http.Request) (dataprovider.EventConditions, error) {
 
- 	var schedules []dataprovider.Schedule
 
- 	var names, groupNames, roleNames, fsPaths []dataprovider.ConditionPattern
 
- 	scheduleHours := r.Form["schedule_hour"]
 
- 	scheduleDayOfWeeks := r.Form["schedule_day_of_week"]
 
- 	scheduleDayOfMonths := r.Form["schedule_day_of_month"]
 
- 	scheduleMonths := r.Form["schedule_month"]
 
- 	for idx, hour := range scheduleHours {
 
- 		if hour != "" {
 
- 			schedules = append(schedules, dataprovider.Schedule{
 
- 				Hours:      hour,
 
- 				DayOfWeek:  scheduleDayOfWeeks[idx],
 
- 				DayOfMonth: scheduleDayOfMonths[idx],
 
- 				Month:      scheduleMonths[idx],
 
- 			})
 
- 		}
 
- 	}
 
- 	for idx, name := range r.Form["name_pattern"] {
 
- 		if name != "" {
 
- 			names = append(names, dataprovider.ConditionPattern{
 
- 				Pattern:      name,
 
- 				InverseMatch: r.Form["type_name_pattern"][idx] == inversePatternType,
 
- 			})
 
- 		}
 
- 	}
 
- 	for idx, name := range r.Form["group_name_pattern"] {
 
- 		if name != "" {
 
- 			groupNames = append(groupNames, dataprovider.ConditionPattern{
 
- 				Pattern:      name,
 
- 				InverseMatch: r.Form["type_group_name_pattern"][idx] == inversePatternType,
 
- 			})
 
- 		}
 
- 	}
 
- 	for idx, name := range r.Form["role_name_pattern"] {
 
- 		if name != "" {
 
- 			roleNames = append(roleNames, dataprovider.ConditionPattern{
 
- 				Pattern:      name,
 
- 				InverseMatch: r.Form["type_role_name_pattern"][idx] == inversePatternType,
 
- 			})
 
- 		}
 
- 	}
 
- 	for idx, name := range r.Form["fs_path_pattern"] {
 
- 		if name != "" {
 
- 			fsPaths = append(fsPaths, dataprovider.ConditionPattern{
 
- 				Pattern:      name,
 
- 				InverseMatch: r.Form["type_fs_path_pattern"][idx] == inversePatternType,
 
- 			})
 
- 		}
 
- 	}
 
- 	minFileSize, err := util.ParseBytes(r.Form.Get("fs_min_size"))
 
- 	if err != nil {
 
- 		return dataprovider.EventConditions{}, util.NewI18nError(fmt.Errorf("invalid min file size: %w", err), util.I18nErrorInvalidMinSize)
 
- 	}
 
- 	maxFileSize, err := util.ParseBytes(r.Form.Get("fs_max_size"))
 
- 	if err != nil {
 
- 		return dataprovider.EventConditions{}, util.NewI18nError(fmt.Errorf("invalid max file size: %w", err), util.I18nErrorInvalidMaxSize)
 
- 	}
 
- 	conditions := dataprovider.EventConditions{
 
- 		FsEvents:       r.Form["fs_events"],
 
- 		ProviderEvents: r.Form["provider_events"],
 
- 		IDPLoginEvent:  getIDPLoginEventFromPostField(r),
 
- 		Schedules:      schedules,
 
- 		Options: dataprovider.ConditionOptions{
 
- 			Names:               names,
 
- 			GroupNames:          groupNames,
 
- 			RoleNames:           roleNames,
 
- 			FsPaths:             fsPaths,
 
- 			Protocols:           r.Form["fs_protocols"],
 
- 			ProviderObjects:     r.Form["provider_objects"],
 
- 			MinFileSize:         minFileSize,
 
- 			MaxFileSize:         maxFileSize,
 
- 			ConcurrentExecution: r.Form.Get("concurrent_execution") != "",
 
- 		},
 
- 	}
 
- 	return conditions, nil
 
- }
 
- func getEventRuleActionsFromPostFields(r *http.Request) []dataprovider.EventAction {
 
- 	var actions []dataprovider.EventAction
 
- 	names := r.Form["action_name"]
 
- 	orders := r.Form["action_order"]
 
- 	for idx, name := range names {
 
- 		if name != "" {
 
- 			order, err := strconv.Atoi(orders[idx])
 
- 			if err == nil {
 
- 				options := r.Form["action_options"+strconv.Itoa(idx)]
 
- 				actions = append(actions, dataprovider.EventAction{
 
- 					BaseEventAction: dataprovider.BaseEventAction{
 
- 						Name: name,
 
- 					},
 
- 					Order: order + 1,
 
- 					Options: dataprovider.EventActionOptions{
 
- 						IsFailureAction: slices.Contains(options, "1"),
 
- 						StopOnFailure:   slices.Contains(options, "2"),
 
- 						ExecuteSync:     slices.Contains(options, "3"),
 
- 					},
 
- 				})
 
- 			}
 
- 		}
 
- 	}
 
- 	return actions
 
- }
 
- func updateRepeaterFormRuleFields(r *http.Request) {
 
- 	for k := range r.Form {
 
- 		if hasPrefixAndSuffix(k, "schedules[", "][schedule_hour]") {
 
- 			base, _ := strings.CutSuffix(k, "[schedule_hour]")
 
- 			r.Form.Add("schedule_hour", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("schedule_day_of_week", strings.TrimSpace(r.Form.Get(base+"[schedule_day_of_week]")))
 
- 			r.Form.Add("schedule_day_of_month", strings.TrimSpace(r.Form.Get(base+"[schedule_day_of_month]")))
 
- 			r.Form.Add("schedule_month", strings.TrimSpace(r.Form.Get(base+"[schedule_month]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "name_filters[", "][name_pattern]") {
 
- 			base, _ := strings.CutSuffix(k, "[name_pattern]")
 
- 			r.Form.Add("name_pattern", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("type_name_pattern", strings.TrimSpace(r.Form.Get(base+"[type_name_pattern]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "group_name_filters[", "][group_name_pattern]") {
 
- 			base, _ := strings.CutSuffix(k, "[group_name_pattern]")
 
- 			r.Form.Add("group_name_pattern", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("type_group_name_pattern", strings.TrimSpace(r.Form.Get(base+"[type_group_name_pattern]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "role_name_filters[", "][role_name_pattern]") {
 
- 			base, _ := strings.CutSuffix(k, "[role_name_pattern]")
 
- 			r.Form.Add("role_name_pattern", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("type_role_name_pattern", strings.TrimSpace(r.Form.Get(base+"[type_role_name_pattern]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "path_filters[", "][fs_path_pattern]") {
 
- 			base, _ := strings.CutSuffix(k, "[fs_path_pattern]")
 
- 			r.Form.Add("fs_path_pattern", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form.Add("type_fs_path_pattern", strings.TrimSpace(r.Form.Get(base+"[type_fs_path_pattern]")))
 
- 			continue
 
- 		}
 
- 		if hasPrefixAndSuffix(k, "actions[", "][action_name]") {
 
- 			base, _ := strings.CutSuffix(k, "[action_name]")
 
- 			order, _ := strings.CutPrefix(k, "actions[")
 
- 			order, _ = strings.CutSuffix(order, "][action_name]")
 
- 			r.Form.Add("action_name", strings.TrimSpace(r.Form.Get(k)))
 
- 			r.Form["action_options"+strconv.Itoa(len(r.Form["action_name"])-1)] = r.Form[base+"[action_options][]"]
 
- 			r.Form.Add("action_order", order)
 
- 			continue
 
- 		}
 
- 	}
 
- }
 
- func getEventRuleFromPostFields(r *http.Request) (dataprovider.EventRule, error) {
 
- 	err := r.ParseForm()
 
- 	if err != nil {
 
- 		return dataprovider.EventRule{}, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	updateRepeaterFormRuleFields(r)
 
- 	status, err := strconv.Atoi(r.Form.Get("status"))
 
- 	if err != nil {
 
- 		return dataprovider.EventRule{}, fmt.Errorf("invalid status: %w", err)
 
- 	}
 
- 	trigger, err := strconv.Atoi(r.Form.Get("trigger"))
 
- 	if err != nil {
 
- 		return dataprovider.EventRule{}, fmt.Errorf("invalid trigger: %w", err)
 
- 	}
 
- 	conditions, err := getEventRuleConditionsFromPostFields(r)
 
- 	if err != nil {
 
- 		return dataprovider.EventRule{}, err
 
- 	}
 
- 	rule := dataprovider.EventRule{
 
- 		Name:        strings.TrimSpace(r.Form.Get("name")),
 
- 		Status:      status,
 
- 		Description: r.Form.Get("description"),
 
- 		Trigger:     trigger,
 
- 		Conditions:  conditions,
 
- 		Actions:     getEventRuleActionsFromPostFields(r),
 
- 	}
 
- 	return rule, nil
 
- }
 
- func getRoleFromPostFields(r *http.Request) (dataprovider.Role, error) {
 
- 	err := r.ParseForm()
 
- 	if err != nil {
 
- 		return dataprovider.Role{}, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	return dataprovider.Role{
 
- 		Name:        strings.TrimSpace(r.Form.Get("name")),
 
- 		Description: r.Form.Get("description"),
 
- 	}, nil
 
- }
 
- func getIPListEntryFromPostFields(r *http.Request, listType dataprovider.IPListType) (dataprovider.IPListEntry, error) {
 
- 	err := r.ParseForm()
 
- 	if err != nil {
 
- 		return dataprovider.IPListEntry{}, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	var mode int
 
- 	if listType == dataprovider.IPListTypeDefender {
 
- 		mode, err = strconv.Atoi(r.Form.Get("mode"))
 
- 		if err != nil {
 
- 			return dataprovider.IPListEntry{}, fmt.Errorf("invalid mode: %w", err)
 
- 		}
 
- 	} else {
 
- 		mode = 1
 
- 	}
 
- 	protocols := 0
 
- 	for _, proto := range r.Form["protocols"] {
 
- 		p, err := strconv.Atoi(proto)
 
- 		if err == nil {
 
- 			protocols += p
 
- 		}
 
- 	}
 
- 	return dataprovider.IPListEntry{
 
- 		IPOrNet:     strings.TrimSpace(r.Form.Get("ipornet")),
 
- 		Mode:        mode,
 
- 		Protocols:   protocols,
 
- 		Description: r.Form.Get("description"),
 
- 	}, nil
 
- }
 
- func getSFTPConfigsFromPostFields(r *http.Request) *dataprovider.SFTPDConfigs {
 
- 	return &dataprovider.SFTPDConfigs{
 
- 		HostKeyAlgos:   r.Form["sftp_host_key_algos"],
 
- 		PublicKeyAlgos: r.Form["sftp_pub_key_algos"],
 
- 		KexAlgorithms:  r.Form["sftp_kex_algos"],
 
- 		Ciphers:        r.Form["sftp_ciphers"],
 
- 		MACs:           r.Form["sftp_macs"],
 
- 	}
 
- }
 
- func getACMEConfigsFromPostFields(r *http.Request) *dataprovider.ACMEConfigs {
 
- 	port, err := strconv.Atoi(r.Form.Get("acme_port"))
 
- 	if err != nil {
 
- 		port = 80
 
- 	}
 
- 	var protocols int
 
- 	for _, val := range r.Form["acme_protocols"] {
 
- 		switch val {
 
- 		case "1":
 
- 			protocols++
 
- 		case "2":
 
- 			protocols += 2
 
- 		case "3":
 
- 			protocols += 4
 
- 		}
 
- 	}
 
- 	return &dataprovider.ACMEConfigs{
 
- 		Domain:          strings.TrimSpace(r.Form.Get("acme_domain")),
 
- 		Email:           strings.TrimSpace(r.Form.Get("acme_email")),
 
- 		HTTP01Challenge: dataprovider.ACMEHTTP01Challenge{Port: port},
 
- 		Protocols:       protocols,
 
- 	}
 
- }
 
- func getSMTPConfigsFromPostFields(r *http.Request) *dataprovider.SMTPConfigs {
 
- 	port, err := strconv.Atoi(r.Form.Get("smtp_port"))
 
- 	if err != nil {
 
- 		port = 587
 
- 	}
 
- 	authType, err := strconv.Atoi(r.Form.Get("smtp_auth"))
 
- 	if err != nil {
 
- 		authType = 0
 
- 	}
 
- 	encryption, err := strconv.Atoi(r.Form.Get("smtp_encryption"))
 
- 	if err != nil {
 
- 		encryption = 0
 
- 	}
 
- 	debug := 0
 
- 	if r.Form.Get("smtp_debug") != "" {
 
- 		debug = 1
 
- 	}
 
- 	oauth2Provider := 0
 
- 	if r.Form.Get("smtp_oauth2_provider") == "1" {
 
- 		oauth2Provider = 1
 
- 	}
 
- 	return &dataprovider.SMTPConfigs{
 
- 		Host:       strings.TrimSpace(r.Form.Get("smtp_host")),
 
- 		Port:       port,
 
- 		From:       strings.TrimSpace(r.Form.Get("smtp_from")),
 
- 		User:       strings.TrimSpace(r.Form.Get("smtp_username")),
 
- 		Password:   getSecretFromFormField(r, "smtp_password"),
 
- 		AuthType:   authType,
 
- 		Encryption: encryption,
 
- 		Domain:     strings.TrimSpace(r.Form.Get("smtp_domain")),
 
- 		Debug:      debug,
 
- 		OAuth2: dataprovider.SMTPOAuth2{
 
- 			Provider:     oauth2Provider,
 
- 			Tenant:       strings.TrimSpace(r.Form.Get("smtp_oauth2_tenant")),
 
- 			ClientID:     strings.TrimSpace(r.Form.Get("smtp_oauth2_client_id")),
 
- 			ClientSecret: getSecretFromFormField(r, "smtp_oauth2_client_secret"),
 
- 			RefreshToken: getSecretFromFormField(r, "smtp_oauth2_refresh_token"),
 
- 		},
 
- 	}
 
- }
 
- func getImageInputBytes(r *http.Request, fieldName, removeFieldName string, defaultVal []byte) ([]byte, error) {
 
- 	var result []byte
 
- 	remove := r.Form.Get(removeFieldName)
 
- 	if remove == "" || remove == "0" {
 
- 		result = defaultVal
 
- 	}
 
- 	f, _, err := r.FormFile(fieldName)
 
- 	if err != nil {
 
- 		if errors.Is(err, http.ErrMissingFile) {
 
- 			return result, nil
 
- 		}
 
- 		return nil, err
 
- 	}
 
- 	defer f.Close()
 
- 	return io.ReadAll(f)
 
- }
 
- func getBrandingConfigFromPostFields(r *http.Request, config *dataprovider.BrandingConfigs) (
 
- 	*dataprovider.BrandingConfigs, error,
 
- ) {
 
- 	if config == nil {
 
- 		config = &dataprovider.BrandingConfigs{}
 
- 	}
 
- 	adminLogo, err := getImageInputBytes(r, "branding_webadmin_logo", "branding_webadmin_logo_remove", config.WebAdmin.Logo)
 
- 	if err != nil {
 
- 		return nil, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	adminFavicon, err := getImageInputBytes(r, "branding_webadmin_favicon", "branding_webadmin_favicon_remove",
 
- 		config.WebAdmin.Favicon)
 
- 	if err != nil {
 
- 		return nil, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	clientLogo, err := getImageInputBytes(r, "branding_webclient_logo", "branding_webclient_logo_remove",
 
- 		config.WebClient.Logo)
 
- 	if err != nil {
 
- 		return nil, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	clientFavicon, err := getImageInputBytes(r, "branding_webclient_favicon", "branding_webclient_favicon_remove",
 
- 		config.WebClient.Favicon)
 
- 	if err != nil {
 
- 		return nil, util.NewI18nError(err, util.I18nErrorInvalidForm)
 
- 	}
 
- 	branding := &dataprovider.BrandingConfigs{
 
- 		WebAdmin: dataprovider.BrandingConfig{
 
- 			Name:           strings.TrimSpace(r.Form.Get("branding_webadmin_name")),
 
- 			ShortName:      strings.TrimSpace(r.Form.Get("branding_webadmin_short_name")),
 
- 			Logo:           adminLogo,
 
- 			Favicon:        adminFavicon,
 
- 			DisclaimerName: strings.TrimSpace(r.Form.Get("branding_webadmin_disclaimer_name")),
 
- 			DisclaimerURL:  strings.TrimSpace(r.Form.Get("branding_webadmin_disclaimer_url")),
 
- 		},
 
- 		WebClient: dataprovider.BrandingConfig{
 
- 			Name:           strings.TrimSpace(r.Form.Get("branding_webclient_name")),
 
- 			ShortName:      strings.TrimSpace(r.Form.Get("branding_webclient_short_name")),
 
- 			Logo:           clientLogo,
 
- 			Favicon:        clientFavicon,
 
- 			DisclaimerName: strings.TrimSpace(r.Form.Get("branding_webclient_disclaimer_name")),
 
- 			DisclaimerURL:  strings.TrimSpace(r.Form.Get("branding_webclient_disclaimer_url")),
 
- 		},
 
- 	}
 
- 	return branding, nil
 
- }
 
- func (s *httpdServer) handleWebAdminForgotPwd(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	if !smtp.IsEnabled() {
 
- 		s.renderNotFoundPage(w, r, errors.New("this page does not exist"))
 
- 		return
 
- 	}
 
- 	s.renderForgotPwdPage(w, r, nil)
 
- }
 
- func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	err := r.ParseForm()
 
- 	if err != nil {
 
- 		s.renderForgotPwdPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm))
 
- 		return
 
- 	}
 
- 	if err := verifyLoginCookieAndCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	err = handleForgotPassword(r, r.Form.Get("username"), true)
 
- 	if err != nil {
 
- 		s.renderForgotPwdPage(w, r, util.NewI18nError(err, util.I18nErrorPwdResetGeneric))
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webAdminResetPwdPath, http.StatusFound)
 
- }
 
- func (s *httpdServer) handleWebAdminPasswordReset(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize)
 
- 	if !smtp.IsEnabled() {
 
- 		s.renderNotFoundPage(w, r, errors.New("this page does not exist"))
 
- 		return
 
- 	}
 
- 	s.renderResetPwdPage(w, r, nil)
 
- }
 
- func (s *httpdServer) handleWebAdminTwoFactor(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	s.renderTwoFactorPage(w, r, nil)
 
- }
 
- func (s *httpdServer) handleWebAdminTwoFactorRecovery(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	s.renderTwoFactorRecoveryPage(w, r, nil)
 
- }
 
- func (s *httpdServer) handleWebAdminMFA(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	s.renderMFAPage(w, r)
 
- }
 
- func (s *httpdServer) handleWebAdminProfile(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	s.renderProfilePage(w, r, nil)
 
- }
 
- func (s *httpdServer) handleWebAdminChangePwd(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	s.renderChangePasswordPage(w, r, nil)
 
- }
 
- func (s *httpdServer) handleWebAdminProfilePost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	err := r.ParseForm()
 
- 	if err != nil {
 
- 		s.renderProfilePage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm))
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderProfilePage(w, r, util.NewI18nError(err, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	admin, err := dataprovider.AdminExists(claims.Username)
 
- 	if err != nil {
 
- 		s.renderProfilePage(w, r, err)
 
- 		return
 
- 	}
 
- 	admin.Filters.AllowAPIKeyAuth = r.Form.Get("allow_api_key_auth") != ""
 
- 	admin.Email = r.Form.Get("email")
 
- 	admin.Description = r.Form.Get("description")
 
- 	err = dataprovider.UpdateAdmin(&admin, dataprovider.ActionExecutorSelf, ipAddr, admin.Role)
 
- 	if err != nil {
 
- 		s.renderProfilePage(w, r, err)
 
- 		return
 
- 	}
 
- 	s.renderMessagePage(w, r, util.I18nProfileTitle, http.StatusOK, nil, util.I18nProfileUpdated)
 
- }
 
- func (s *httpdServer) handleWebMaintenance(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	s.renderMaintenancePage(w, r, nil)
 
- }
 
- func (s *httpdServer) handleWebRestore(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, MaxRestoreSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	err = r.ParseMultipartForm(MaxRestoreSize)
 
- 	if err != nil {
 
- 		s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm))
 
- 		return
 
- 	}
 
- 	defer r.MultipartForm.RemoveAll() //nolint:errcheck
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	restoreMode, err := strconv.Atoi(r.Form.Get("mode"))
 
- 	if err != nil {
 
- 		s.renderMaintenancePage(w, r, err)
 
- 		return
 
- 	}
 
- 	scanQuota, err := strconv.Atoi(r.Form.Get("quota"))
 
- 	if err != nil {
 
- 		s.renderMaintenancePage(w, r, err)
 
- 		return
 
- 	}
 
- 	backupFile, _, err := r.FormFile("backup_file")
 
- 	if err != nil {
 
- 		s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorBackupFile))
 
- 		return
 
- 	}
 
- 	defer backupFile.Close()
 
- 	backupContent, err := io.ReadAll(backupFile)
 
- 	if err != nil || len(backupContent) == 0 {
 
- 		if len(backupContent) == 0 {
 
- 			err = errors.New("backup file size must be greater than 0")
 
- 		}
 
- 		s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorBackupFile))
 
- 		return
 
- 	}
 
- 	if err := restoreBackup(backupContent, "", scanQuota, restoreMode, claims.Username, ipAddr, claims.Role); err != nil {
 
- 		s.renderMaintenancePage(w, r, util.NewI18nError(err, util.I18nErrorRestore))
 
- 		return
 
- 	}
 
- 	s.renderMessagePage(w, r, util.I18nMaintenanceTitle, http.StatusOK, nil, util.I18nBackupOK)
 
- }
 
- func getAllAdmins(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		sendAPIResponse(w, r, nil, util.I18nErrorInvalidToken, http.StatusForbidden)
 
- 		return
 
- 	}
 
- 	dataGetter := func(limit, offset int) ([]byte, int, error) {
 
- 		results, err := dataprovider.GetAdmins(limit, offset, dataprovider.OrderASC)
 
- 		if err != nil {
 
- 			return nil, 0, err
 
- 		}
 
- 		data, err := json.Marshal(results)
 
- 		return data, len(results), err
 
- 	}
 
- 	streamJSONArray(w, defaultQueryLimit, dataGetter)
 
- }
 
- func (s *httpdServer) handleGetWebAdmins(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	data := s.getBasePageData(util.I18nAdminsTitle, webAdminsPath, w, r)
 
- 	renderAdminTemplate(w, templateAdmins, data)
 
- }
 
- func (s *httpdServer) handleWebAdminSetupGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxLoginBodySize)
 
- 	if dataprovider.HasAdmin() {
 
- 		http.Redirect(w, r, webAdminLoginPath, http.StatusFound)
 
- 		return
 
- 	}
 
- 	s.renderAdminSetupPage(w, r, "", nil)
 
- }
 
- func (s *httpdServer) handleWebAddAdminGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	admin := &dataprovider.Admin{
 
- 		Status:      1,
 
- 		Permissions: []string{dataprovider.PermAdminAny},
 
- 	}
 
- 	s.renderAddUpdateAdminPage(w, r, admin, nil, true)
 
- }
 
- func (s *httpdServer) handleWebUpdateAdminGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	username := getURLParam(r, "username")
 
- 	admin, err := dataprovider.AdminExists(username)
 
- 	if err == nil {
 
- 		s.renderAddUpdateAdminPage(w, r, &admin, nil, false)
 
- 	} else if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 	} else {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebAddAdminPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	admin, err := getAdminFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderAddUpdateAdminPage(w, r, &admin, err, true)
 
- 		return
 
- 	}
 
- 	if admin.Password == "" && s.binding.isWebAdminLoginFormDisabled() {
 
- 		admin.Password = util.GenerateUniqueID()
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	err = dataprovider.AddAdmin(&admin, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderAddUpdateAdminPage(w, r, &admin, err, true)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webAdminsPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebUpdateAdminPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	username := getURLParam(r, "username")
 
- 	admin, err := dataprovider.AdminExists(username)
 
- 	if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 		return
 
- 	} else if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	updatedAdmin, err := getAdminFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderAddUpdateAdminPage(w, r, &updatedAdmin, err, false)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	updatedAdmin.ID = admin.ID
 
- 	updatedAdmin.Username = admin.Username
 
- 	if updatedAdmin.Password == "" {
 
- 		updatedAdmin.Password = admin.Password
 
- 	}
 
- 	updatedAdmin.Filters.TOTPConfig = admin.Filters.TOTPConfig
 
- 	updatedAdmin.Filters.RecoveryCodes = admin.Filters.RecoveryCodes
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderAddUpdateAdminPage(w, r, &updatedAdmin, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken), false)
 
- 		return
 
- 	}
 
- 	if username == claims.Username {
 
- 		if claims.isCriticalPermRemoved(updatedAdmin.Permissions) {
 
- 			s.renderAddUpdateAdminPage(w, r, &updatedAdmin,
 
- 				util.NewI18nError(errors.New("you cannot remove these permissions to yourself"),
 
- 					util.I18nErrorAdminSelfPerms,
 
- 				), false)
 
- 			return
 
- 		}
 
- 		if updatedAdmin.Status == 0 {
 
- 			s.renderAddUpdateAdminPage(w, r, &updatedAdmin,
 
- 				util.NewI18nError(errors.New("you cannot disable yourself"),
 
- 					util.I18nErrorAdminSelfDisable,
 
- 				), false)
 
- 			return
 
- 		}
 
- 		if updatedAdmin.Role != claims.Role {
 
- 			s.renderAddUpdateAdminPage(w, r, &updatedAdmin,
 
- 				util.NewI18nError(
 
- 					errors.New("you cannot add/change your role"),
 
- 					util.I18nErrorAdminSelfRole,
 
- 				), false)
 
- 			return
 
- 		}
 
- 		updatedAdmin.Filters.RequirePasswordChange = admin.Filters.RequirePasswordChange
 
- 		updatedAdmin.Filters.RequireTwoFactor = admin.Filters.RequireTwoFactor
 
- 	}
 
- 	err = dataprovider.UpdateAdmin(&updatedAdmin, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderAddUpdateAdminPage(w, r, &updatedAdmin, err, false)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webAdminsPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebDefenderPage(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	data := defenderHostsPage{
 
- 		basePage:         s.getBasePageData(util.I18nDefenderTitle, webDefenderPath, w, r),
 
- 		DefenderHostsURL: webDefenderHostsPath,
 
- 	}
 
- 	renderAdminTemplate(w, templateDefender, data)
 
- }
 
- func getAllUsers(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		sendAPIResponse(w, r, nil, util.I18nErrorInvalidToken, http.StatusForbidden)
 
- 		return
 
- 	}
 
- 	dataGetter := func(limit, offset int) ([]byte, int, error) {
 
- 		results, err := dataprovider.GetUsers(limit, offset, dataprovider.OrderASC, claims.Role)
 
- 		if err != nil {
 
- 			return nil, 0, err
 
- 		}
 
- 		data, err := json.Marshal(results)
 
- 		return data, len(results), err
 
- 	}
 
- 	streamJSONArray(w, defaultQueryLimit, dataGetter)
 
- }
 
- func (s *httpdServer) handleGetWebUsers(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	data := s.getBasePageData(util.I18nUsersTitle, webUsersPath, w, r)
 
- 	renderAdminTemplate(w, templateUsers, data)
 
- }
 
- func (s *httpdServer) handleWebTemplateFolderGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	if r.URL.Query().Get("from") != "" {
 
- 		name := r.URL.Query().Get("from")
 
- 		folder, err := dataprovider.GetFolderByName(name)
 
- 		if err == nil {
 
- 			folder.FsConfig.SetEmptySecrets()
 
- 			s.renderFolderPage(w, r, folder, folderPageModeTemplate, nil)
 
- 		} else if errors.Is(err, util.ErrNotFound) {
 
- 			s.renderNotFoundPage(w, r, err)
 
- 		} else {
 
- 			s.renderInternalServerErrorPage(w, r, err)
 
- 		}
 
- 	} else {
 
- 		folder := vfs.BaseVirtualFolder{}
 
- 		s.renderFolderPage(w, r, folder, folderPageModeTemplate, nil)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebTemplateFolderPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	templateFolder := vfs.BaseVirtualFolder{}
 
- 	err = r.ParseMultipartForm(maxRequestSize)
 
- 	if err != nil {
 
- 		s.renderMessagePage(w, r, util.I18nTemplateFolderTitle, http.StatusBadRequest, util.NewI18nError(err, util.I18nErrorInvalidForm), "")
 
- 		return
 
- 	}
 
- 	defer r.MultipartForm.RemoveAll() //nolint:errcheck
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	templateFolder.MappedPath = r.Form.Get("mapped_path")
 
- 	templateFolder.Description = r.Form.Get("description")
 
- 	fsConfig, err := getFsConfigFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderMessagePage(w, r, util.I18nTemplateFolderTitle, http.StatusBadRequest, err, "")
 
- 		return
 
- 	}
 
- 	templateFolder.FsConfig = fsConfig
 
- 	var dump dataprovider.BackupData
 
- 	dump.Version = dataprovider.DumpVersion
 
- 	foldersFields := getFoldersForTemplate(r)
 
- 	for _, tmpl := range foldersFields {
 
- 		f := getFolderFromTemplate(templateFolder, tmpl)
 
- 		if err := dataprovider.ValidateFolder(&f); err != nil {
 
- 			s.renderMessagePage(w, r, util.I18nTemplateFolderTitle, http.StatusBadRequest, err, "")
 
- 			return
 
- 		}
 
- 		dump.Folders = append(dump.Folders, f)
 
- 	}
 
- 	if len(dump.Folders) == 0 {
 
- 		s.renderMessagePage(w, r, util.I18nTemplateFolderTitle, http.StatusBadRequest,
 
- 			util.NewI18nError(
 
- 				errors.New("no valid folder defined, unable to complete the requested action"),
 
- 				util.I18nErrorFolderTemplate,
 
- 			), "")
 
- 		return
 
- 	}
 
- 	if r.Form.Get("form_action") == "export_from_template" {
 
- 		w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"sftpgo-%v-folders-from-template.json\"",
 
- 			len(dump.Folders)))
 
- 		render.JSON(w, r, dump)
 
- 		return
 
- 	}
 
- 	if err = RestoreFolders(dump.Folders, "", 1, 0, claims.Username, ipAddr, claims.Role); err != nil {
 
- 		s.renderMessagePage(w, r, util.I18nTemplateFolderTitle, getRespStatus(err), err, "")
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebTemplateUserGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	tokenAdmin := getAdminFromToken(r)
 
- 	admin, err := dataprovider.AdminExists(tokenAdmin.Username)
 
- 	if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, fmt.Errorf("unable to get the admin %q: %w", tokenAdmin.Username, err))
 
- 		return
 
- 	}
 
- 	if r.URL.Query().Get("from") != "" {
 
- 		username := r.URL.Query().Get("from")
 
- 		user, err := dataprovider.UserExists(username, admin.Role)
 
- 		if err == nil {
 
- 			user.SetEmptySecrets()
 
- 			user.PublicKeys = nil
 
- 			user.Email = ""
 
- 			user.Description = ""
 
- 			if user.ExpirationDate == 0 && admin.Filters.Preferences.DefaultUsersExpiration > 0 {
 
- 				user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour * time.Duration(admin.Filters.Preferences.DefaultUsersExpiration)))
 
- 			}
 
- 			s.renderUserPage(w, r, &user, userPageModeTemplate, nil, &admin)
 
- 		} else if errors.Is(err, util.ErrNotFound) {
 
- 			s.renderNotFoundPage(w, r, err)
 
- 		} else {
 
- 			s.renderInternalServerErrorPage(w, r, err)
 
- 		}
 
- 	} else {
 
- 		user := dataprovider.User{BaseUser: sdk.BaseUser{
 
- 			Status: 1,
 
- 			Permissions: map[string][]string{
 
- 				"/": {dataprovider.PermAny},
 
- 			},
 
- 		}}
 
- 		if admin.Filters.Preferences.DefaultUsersExpiration > 0 {
 
- 			user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour * time.Duration(admin.Filters.Preferences.DefaultUsersExpiration)))
 
- 		}
 
- 		s.renderUserPage(w, r, &user, userPageModeTemplate, nil, &admin)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebTemplateUserPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	templateUser, err := getUserFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderMessagePage(w, r, util.I18nTemplateUserTitle, http.StatusBadRequest, err, "")
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	var dump dataprovider.BackupData
 
- 	dump.Version = dataprovider.DumpVersion
 
- 	userTmplFields := getUsersForTemplate(r)
 
- 	for _, tmpl := range userTmplFields {
 
- 		u := getUserFromTemplate(templateUser, tmpl)
 
- 		if err := dataprovider.ValidateUser(&u); err != nil {
 
- 			s.renderMessagePage(w, r, util.I18nTemplateUserTitle, http.StatusBadRequest, err, "")
 
- 			return
 
- 		}
 
- 		// to create a template the "manage_system" permission is required, so role admins cannot use
 
- 		// this method, we don't need to force the role
 
- 		dump.Users = append(dump.Users, u)
 
- 		for _, folder := range u.VirtualFolders {
 
- 			if !dump.HasFolder(folder.Name) {
 
- 				dump.Folders = append(dump.Folders, folder.BaseVirtualFolder)
 
- 			}
 
- 		}
 
- 	}
 
- 	if len(dump.Users) == 0 {
 
- 		s.renderMessagePage(w, r, util.I18nTemplateUserTitle,
 
- 			http.StatusBadRequest, util.NewI18nError(
 
- 				errors.New("no valid user defined, unable to complete the requested action"),
 
- 				util.I18nErrorUserTemplate,
 
- 			), "")
 
- 		return
 
- 	}
 
- 	if r.Form.Get("form_action") == "export_from_template" {
 
- 		w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"sftpgo-%v-users-from-template.json\"",
 
- 			len(dump.Users)))
 
- 		render.JSON(w, r, dump)
 
- 		return
 
- 	}
 
- 	if err = RestoreUsers(dump.Users, "", 1, 0, claims.Username, ipAddr, claims.Role); err != nil {
 
- 		s.renderMessagePage(w, r, util.I18nTemplateUserTitle, getRespStatus(err), err, "")
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebAddUserGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	tokenAdmin := getAdminFromToken(r)
 
- 	admin, err := dataprovider.AdminExists(tokenAdmin.Username)
 
- 	if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, fmt.Errorf("unable to get the admin %q: %w", tokenAdmin.Username, err))
 
- 		return
 
- 	}
 
- 	user := dataprovider.User{BaseUser: sdk.BaseUser{
 
- 		Status: 1,
 
- 		Permissions: map[string][]string{
 
- 			"/": {dataprovider.PermAny},
 
- 		}},
 
- 	}
 
- 	if admin.Filters.Preferences.DefaultUsersExpiration > 0 {
 
- 		user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour * time.Duration(admin.Filters.Preferences.DefaultUsersExpiration)))
 
- 	}
 
- 	s.renderUserPage(w, r, &user, userPageModeAdd, nil, &admin)
 
- }
 
- func (s *httpdServer) handleWebUpdateUserGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	username := getURLParam(r, "username")
 
- 	user, err := dataprovider.UserExists(username, claims.Role)
 
- 	if err == nil {
 
- 		s.renderUserPage(w, r, &user, userPageModeUpdate, nil, nil)
 
- 	} else if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 	} else {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebAddUserPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	user, err := getUserFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderUserPage(w, r, &user, userPageModeAdd, err, nil)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	user = getUserFromTemplate(user, userTemplateFields{
 
- 		Username:   user.Username,
 
- 		Password:   user.Password,
 
- 		PublicKeys: user.PublicKeys,
 
- 	})
 
- 	if claims.Role != "" {
 
- 		user.Role = claims.Role
 
- 	}
 
- 	user.Filters.RecoveryCodes = nil
 
- 	user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
 
- 		Enabled: false,
 
- 	}
 
- 	err = dataprovider.AddUser(&user, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderUserPage(w, r, &user, userPageModeAdd, err, nil)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebUpdateUserPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	username := getURLParam(r, "username")
 
- 	user, err := dataprovider.UserExists(username, claims.Role)
 
- 	if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 		return
 
- 	} else if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	updatedUser, err := getUserFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderUserPage(w, r, &user, userPageModeUpdate, err, nil)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	updatedUser.ID = user.ID
 
- 	updatedUser.Username = user.Username
 
- 	updatedUser.Filters.RecoveryCodes = user.Filters.RecoveryCodes
 
- 	updatedUser.Filters.TOTPConfig = user.Filters.TOTPConfig
 
- 	updatedUser.LastPasswordChange = user.LastPasswordChange
 
- 	updatedUser.SetEmptySecretsIfNil()
 
- 	if updatedUser.Password == redactedSecret {
 
- 		updatedUser.Password = user.Password
 
- 	}
 
- 	updateEncryptedSecrets(&updatedUser.FsConfig, &user.FsConfig)
 
- 	updatedUser = getUserFromTemplate(updatedUser, userTemplateFields{
 
- 		Username:   updatedUser.Username,
 
- 		Password:   updatedUser.Password,
 
- 		PublicKeys: updatedUser.PublicKeys,
 
- 	})
 
- 	if claims.Role != "" {
 
- 		updatedUser.Role = claims.Role
 
- 	}
 
- 	err = dataprovider.UpdateUser(&updatedUser, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderUserPage(w, r, &updatedUser, userPageModeUpdate, err, nil)
 
- 		return
 
- 	}
 
- 	if r.Form.Get("disconnect") != "" {
 
- 		disconnectUser(user.Username, claims.Username, claims.Role)
 
- 	}
 
- 	http.Redirect(w, r, webUsersPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebGetStatus(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	data := statusPage{
 
- 		basePage: s.getBasePageData(util.I18nStatusTitle, webStatusPath, w, r),
 
- 		Status:   getServicesStatus(),
 
- 	}
 
- 	renderAdminTemplate(w, templateStatus, data)
 
- }
 
- func (s *httpdServer) handleWebGetConnections(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	data := s.getBasePageData(util.I18nSessionsTitle, webConnectionsPath, w, r)
 
- 	renderAdminTemplate(w, templateConnections, data)
 
- }
 
- func (s *httpdServer) handleWebAddFolderGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	s.renderFolderPage(w, r, vfs.BaseVirtualFolder{}, folderPageModeAdd, nil)
 
- }
 
- func (s *httpdServer) handleWebAddFolderPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	folder := vfs.BaseVirtualFolder{}
 
- 	err = r.ParseMultipartForm(maxRequestSize)
 
- 	if err != nil {
 
- 		s.renderFolderPage(w, r, folder, folderPageModeAdd, util.NewI18nError(err, util.I18nErrorInvalidForm))
 
- 		return
 
- 	}
 
- 	defer r.MultipartForm.RemoveAll() //nolint:errcheck
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	folder.MappedPath = strings.TrimSpace(r.Form.Get("mapped_path"))
 
- 	folder.Name = strings.TrimSpace(r.Form.Get("name"))
 
- 	folder.Description = r.Form.Get("description")
 
- 	fsConfig, err := getFsConfigFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderFolderPage(w, r, folder, folderPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	folder.FsConfig = fsConfig
 
- 	folder = getFolderFromTemplate(folder, folder.Name)
 
- 	err = dataprovider.AddFolder(&folder, claims.Username, ipAddr, claims.Role)
 
- 	if err == nil {
 
- 		http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
 
- 	} else {
 
- 		s.renderFolderPage(w, r, folder, folderPageModeAdd, err)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebUpdateFolderGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	name := getURLParam(r, "name")
 
- 	folder, err := dataprovider.GetFolderByName(name)
 
- 	if err == nil {
 
- 		s.renderFolderPage(w, r, folder, folderPageModeUpdate, nil)
 
- 	} else if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 	} else {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebUpdateFolderPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	name := getURLParam(r, "name")
 
- 	folder, err := dataprovider.GetFolderByName(name)
 
- 	if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 		return
 
- 	} else if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	err = r.ParseMultipartForm(maxRequestSize)
 
- 	if err != nil {
 
- 		s.renderFolderPage(w, r, folder, folderPageModeUpdate, util.NewI18nError(err, util.I18nErrorInvalidForm))
 
- 		return
 
- 	}
 
- 	defer r.MultipartForm.RemoveAll() //nolint:errcheck
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	fsConfig, err := getFsConfigFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderFolderPage(w, r, folder, folderPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	updatedFolder := vfs.BaseVirtualFolder{
 
- 		MappedPath:  strings.TrimSpace(r.Form.Get("mapped_path")),
 
- 		Description: r.Form.Get("description"),
 
- 	}
 
- 	updatedFolder.ID = folder.ID
 
- 	updatedFolder.Name = folder.Name
 
- 	updatedFolder.FsConfig = fsConfig
 
- 	updatedFolder.FsConfig.SetEmptySecretsIfNil()
 
- 	updateEncryptedSecrets(&updatedFolder.FsConfig, &folder.FsConfig)
 
- 	updatedFolder = getFolderFromTemplate(updatedFolder, updatedFolder.Name)
 
- 	err = dataprovider.UpdateFolder(&updatedFolder, folder.Users, folder.Groups, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderFolderPage(w, r, updatedFolder, folderPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webFoldersPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) getWebVirtualFolders(w http.ResponseWriter, r *http.Request, limit int, minimal bool) ([]vfs.BaseVirtualFolder, error) {
 
- 	folders := make([]vfs.BaseVirtualFolder, 0, 50)
 
- 	for {
 
- 		f, err := dataprovider.GetFolders(limit, len(folders), dataprovider.OrderASC, minimal)
 
- 		if err != nil {
 
- 			s.renderInternalServerErrorPage(w, r, err)
 
- 			return folders, err
 
- 		}
 
- 		folders = append(folders, f...)
 
- 		if len(f) < limit {
 
- 			break
 
- 		}
 
- 	}
 
- 	return folders, nil
 
- }
 
- func getAllFolders(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	dataGetter := func(limit, offset int) ([]byte, int, error) {
 
- 		results, err := dataprovider.GetFolders(limit, offset, dataprovider.OrderASC, false)
 
- 		if err != nil {
 
- 			return nil, 0, err
 
- 		}
 
- 		data, err := json.Marshal(results)
 
- 		return data, len(results), err
 
- 	}
 
- 	streamJSONArray(w, defaultQueryLimit, dataGetter)
 
- }
 
- func (s *httpdServer) handleWebGetFolders(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	data := s.getBasePageData(util.I18nFoldersTitle, webFoldersPath, w, r)
 
- 	renderAdminTemplate(w, templateFolders, data)
 
- }
 
- func (s *httpdServer) getWebGroups(w http.ResponseWriter, r *http.Request, limit int, minimal bool) ([]dataprovider.Group, error) {
 
- 	groups := make([]dataprovider.Group, 0, 50)
 
- 	for {
 
- 		f, err := dataprovider.GetGroups(limit, len(groups), dataprovider.OrderASC, minimal)
 
- 		if err != nil {
 
- 			s.renderInternalServerErrorPage(w, r, err)
 
- 			return groups, err
 
- 		}
 
- 		groups = append(groups, f...)
 
- 		if len(f) < limit {
 
- 			break
 
- 		}
 
- 	}
 
- 	return groups, nil
 
- }
 
- func getAllGroups(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	dataGetter := func(limit, offset int) ([]byte, int, error) {
 
- 		results, err := dataprovider.GetGroups(limit, offset, dataprovider.OrderASC, false)
 
- 		if err != nil {
 
- 			return nil, 0, err
 
- 		}
 
- 		data, err := json.Marshal(results)
 
- 		return data, len(results), err
 
- 	}
 
- 	streamJSONArray(w, defaultQueryLimit, dataGetter)
 
- }
 
- func (s *httpdServer) handleWebGetGroups(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	data := s.getBasePageData(util.I18nGroupsTitle, webGroupsPath, w, r)
 
- 	renderAdminTemplate(w, templateGroups, data)
 
- }
 
- func (s *httpdServer) handleWebAddGroupGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	s.renderGroupPage(w, r, dataprovider.Group{}, genericPageModeAdd, nil)
 
- }
 
- func (s *httpdServer) handleWebAddGroupPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	group, err := getGroupFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderGroupPage(w, r, group, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	err = dataprovider.AddGroup(&group, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderGroupPage(w, r, group, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webGroupsPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebUpdateGroupGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	name := getURLParam(r, "name")
 
- 	group, err := dataprovider.GroupExists(name)
 
- 	if err == nil {
 
- 		s.renderGroupPage(w, r, group, genericPageModeUpdate, nil)
 
- 	} else if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 	} else {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebUpdateGroupPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	name := getURLParam(r, "name")
 
- 	group, err := dataprovider.GroupExists(name)
 
- 	if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 		return
 
- 	} else if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	updatedGroup, err := getGroupFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderGroupPage(w, r, group, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	updatedGroup.ID = group.ID
 
- 	updatedGroup.Name = group.Name
 
- 	updatedGroup.SetEmptySecretsIfNil()
 
- 	updateEncryptedSecrets(&updatedGroup.UserSettings.FsConfig, &group.UserSettings.FsConfig)
 
- 	err = dataprovider.UpdateGroup(&updatedGroup, group.Users, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderGroupPage(w, r, updatedGroup, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webGroupsPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) getWebEventActions(w http.ResponseWriter, r *http.Request, limit int, minimal bool,
 
- ) ([]dataprovider.BaseEventAction, error) {
 
- 	actions := make([]dataprovider.BaseEventAction, 0, limit)
 
- 	for {
 
- 		res, err := dataprovider.GetEventActions(limit, len(actions), dataprovider.OrderASC, minimal)
 
- 		if err != nil {
 
- 			s.renderInternalServerErrorPage(w, r, err)
 
- 			return actions, err
 
- 		}
 
- 		actions = append(actions, res...)
 
- 		if len(res) < limit {
 
- 			break
 
- 		}
 
- 	}
 
- 	return actions, nil
 
- }
 
- func getAllActions(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	dataGetter := func(limit, offset int) ([]byte, int, error) {
 
- 		results, err := dataprovider.GetEventActions(limit, offset, dataprovider.OrderASC, false)
 
- 		if err != nil {
 
- 			return nil, 0, err
 
- 		}
 
- 		data, err := json.Marshal(results)
 
- 		return data, len(results), err
 
- 	}
 
- 	streamJSONArray(w, defaultQueryLimit, dataGetter)
 
- }
 
- func (s *httpdServer) handleWebGetEventActions(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	data := s.getBasePageData(util.I18nActionsTitle, webAdminEventActionsPath, w, r)
 
- 	renderAdminTemplate(w, templateEventActions, data)
 
- }
 
- func (s *httpdServer) handleWebAddEventActionGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	action := dataprovider.BaseEventAction{
 
- 		Type: dataprovider.ActionTypeHTTP,
 
- 	}
 
- 	s.renderEventActionPage(w, r, action, genericPageModeAdd, nil)
 
- }
 
- func (s *httpdServer) handleWebAddEventActionPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	action, err := getEventActionFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderEventActionPage(w, r, action, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	if err = dataprovider.AddEventAction(&action, claims.Username, ipAddr, claims.Role); err != nil {
 
- 		s.renderEventActionPage(w, r, action, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webAdminEventActionsPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebUpdateEventActionGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	name := getURLParam(r, "name")
 
- 	action, err := dataprovider.EventActionExists(name)
 
- 	if err == nil {
 
- 		s.renderEventActionPage(w, r, action, genericPageModeUpdate, nil)
 
- 	} else if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 	} else {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebUpdateEventActionPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	name := getURLParam(r, "name")
 
- 	action, err := dataprovider.EventActionExists(name)
 
- 	if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 		return
 
- 	} else if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	updatedAction, err := getEventActionFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderEventActionPage(w, r, updatedAction, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	updatedAction.ID = action.ID
 
- 	updatedAction.Name = action.Name
 
- 	updatedAction.Options.SetEmptySecretsIfNil()
 
- 	switch updatedAction.Type {
 
- 	case dataprovider.ActionTypeHTTP:
 
- 		if updatedAction.Options.HTTPConfig.Password.IsNotPlainAndNotEmpty() {
 
- 			updatedAction.Options.HTTPConfig.Password = action.Options.HTTPConfig.Password
 
- 		}
 
- 	}
 
- 	err = dataprovider.UpdateEventAction(&updatedAction, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderEventActionPage(w, r, updatedAction, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webAdminEventActionsPath, http.StatusSeeOther)
 
- }
 
- func getAllRules(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	dataGetter := func(limit, offset int) ([]byte, int, error) {
 
- 		results, err := dataprovider.GetEventRules(limit, offset, dataprovider.OrderASC)
 
- 		if err != nil {
 
- 			return nil, 0, err
 
- 		}
 
- 		data, err := json.Marshal(results)
 
- 		return data, len(results), err
 
- 	}
 
- 	streamJSONArray(w, defaultQueryLimit, dataGetter)
 
- }
 
- func (s *httpdServer) handleWebGetEventRules(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	data := s.getBasePageData(util.I18nRulesTitle, webAdminEventRulesPath, w, r)
 
- 	renderAdminTemplate(w, templateEventRules, data)
 
- }
 
- func (s *httpdServer) handleWebAddEventRuleGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	rule := dataprovider.EventRule{
 
- 		Status:  1,
 
- 		Trigger: dataprovider.EventTriggerFsEvent,
 
- 	}
 
- 	s.renderEventRulePage(w, r, rule, genericPageModeAdd, nil)
 
- }
 
- func (s *httpdServer) handleWebAddEventRulePost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	rule, err := getEventRuleFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderEventRulePage(w, r, rule, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	err = verifyCSRFToken(r, s.csrfTokenAuth)
 
- 	if err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	if err = dataprovider.AddEventRule(&rule, claims.Username, ipAddr, claims.Role); err != nil {
 
- 		s.renderEventRulePage(w, r, rule, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webAdminEventRulesPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebUpdateEventRuleGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	name := getURLParam(r, "name")
 
- 	rule, err := dataprovider.EventRuleExists(name)
 
- 	if err == nil {
 
- 		s.renderEventRulePage(w, r, rule, genericPageModeUpdate, nil)
 
- 	} else if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 	} else {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebUpdateEventRulePost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	name := getURLParam(r, "name")
 
- 	rule, err := dataprovider.EventRuleExists(name)
 
- 	if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 		return
 
- 	} else if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	updatedRule, err := getEventRuleFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderEventRulePage(w, r, updatedRule, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	updatedRule.ID = rule.ID
 
- 	updatedRule.Name = rule.Name
 
- 	err = dataprovider.UpdateEventRule(&updatedRule, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderEventRulePage(w, r, updatedRule, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webAdminEventRulesPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) getWebRoles(w http.ResponseWriter, r *http.Request, limit int, minimal bool) ([]dataprovider.Role, error) {
 
- 	roles := make([]dataprovider.Role, 0, 10)
 
- 	for {
 
- 		res, err := dataprovider.GetRoles(limit, len(roles), dataprovider.OrderASC, minimal)
 
- 		if err != nil {
 
- 			s.renderInternalServerErrorPage(w, r, err)
 
- 			return roles, err
 
- 		}
 
- 		roles = append(roles, res...)
 
- 		if len(res) < limit {
 
- 			break
 
- 		}
 
- 	}
 
- 	return roles, nil
 
- }
 
- func getAllRoles(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	dataGetter := func(limit, offset int) ([]byte, int, error) {
 
- 		results, err := dataprovider.GetRoles(limit, offset, dataprovider.OrderASC, false)
 
- 		if err != nil {
 
- 			return nil, 0, err
 
- 		}
 
- 		data, err := json.Marshal(results)
 
- 		return data, len(results), err
 
- 	}
 
- 	streamJSONArray(w, defaultQueryLimit, dataGetter)
 
- }
 
- func (s *httpdServer) handleWebGetRoles(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	data := s.getBasePageData(util.I18nRolesTitle, webAdminRolesPath, w, r)
 
- 	renderAdminTemplate(w, templateRoles, data)
 
- }
 
- func (s *httpdServer) handleWebAddRoleGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	s.renderRolePage(w, r, dataprovider.Role{}, genericPageModeAdd, nil)
 
- }
 
- func (s *httpdServer) handleWebAddRolePost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	role, err := getRoleFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderRolePage(w, r, role, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	err = dataprovider.AddRole(&role, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderRolePage(w, r, role, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webAdminRolesPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebUpdateRoleGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	role, err := dataprovider.RoleExists(getURLParam(r, "name"))
 
- 	if err == nil {
 
- 		s.renderRolePage(w, r, role, genericPageModeUpdate, nil)
 
- 	} else if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 	} else {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebUpdateRolePost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	role, err := dataprovider.RoleExists(getURLParam(r, "name"))
 
- 	if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 		return
 
- 	} else if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	updatedRole, err := getRoleFromPostFields(r)
 
- 	if err != nil {
 
- 		s.renderRolePage(w, r, role, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	updatedRole.ID = role.ID
 
- 	updatedRole.Name = role.Name
 
- 	err = dataprovider.UpdateRole(&updatedRole, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderRolePage(w, r, updatedRole, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webAdminRolesPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebGetEvents(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	data := eventsPage{
 
- 		basePage:                s.getBasePageData(util.I18nEventsTitle, webEventsPath, w, r),
 
- 		FsEventsSearchURL:       webEventsFsSearchPath,
 
- 		ProviderEventsSearchURL: webEventsProviderSearchPath,
 
- 		LogEventsSearchURL:      webEventsLogSearchPath,
 
- 	}
 
- 	renderAdminTemplate(w, templateEvents, data)
 
- }
 
- func (s *httpdServer) handleWebIPListsPage(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	rtlStatus, rtlProtocols := common.Config.GetRateLimitersStatus()
 
- 	data := ipListsPage{
 
- 		basePage:              s.getBasePageData(util.I18nIPListsTitle, webIPListsPath, w, r),
 
- 		RateLimitersStatus:    rtlStatus,
 
- 		RateLimitersProtocols: strings.Join(rtlProtocols, ", "),
 
- 		IsAllowListEnabled:    common.Config.IsAllowListEnabled(),
 
- 	}
 
- 	renderAdminTemplate(w, templateIPLists, data)
 
- }
 
- func (s *httpdServer) handleWebAddIPListEntryGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	listType, _, err := getIPListPathParams(r)
 
- 	if err != nil {
 
- 		s.renderBadRequestPage(w, r, err)
 
- 		return
 
- 	}
 
- 	s.renderIPListPage(w, r, dataprovider.IPListEntry{Type: listType}, genericPageModeAdd, nil)
 
- }
 
- func (s *httpdServer) handleWebAddIPListEntryPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	listType, _, err := getIPListPathParams(r)
 
- 	if err != nil {
 
- 		s.renderBadRequestPage(w, r, err)
 
- 		return
 
- 	}
 
- 	entry, err := getIPListEntryFromPostFields(r, listType)
 
- 	if err != nil {
 
- 		s.renderIPListPage(w, r, entry, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	entry.Type = listType
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	err = dataprovider.AddIPListEntry(&entry, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderIPListPage(w, r, entry, genericPageModeAdd, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webIPListsPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebUpdateIPListEntryGet(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	listType, ipOrNet, err := getIPListPathParams(r)
 
- 	if err != nil {
 
- 		s.renderBadRequestPage(w, r, err)
 
- 		return
 
- 	}
 
- 	entry, err := dataprovider.IPListEntryExists(ipOrNet, listType)
 
- 	if err == nil {
 
- 		s.renderIPListPage(w, r, entry, genericPageModeUpdate, nil)
 
- 	} else if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 	} else {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 	}
 
- }
 
- func (s *httpdServer) handleWebUpdateIPListEntryPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	listType, ipOrNet, err := getIPListPathParams(r)
 
- 	if err != nil {
 
- 		s.renderBadRequestPage(w, r, err)
 
- 		return
 
- 	}
 
- 	entry, err := dataprovider.IPListEntryExists(ipOrNet, listType)
 
- 	if errors.Is(err, util.ErrNotFound) {
 
- 		s.renderNotFoundPage(w, r, err)
 
- 		return
 
- 	} else if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	updatedEntry, err := getIPListEntryFromPostFields(r, listType)
 
- 	if err != nil {
 
- 		s.renderIPListPage(w, r, entry, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	updatedEntry.Type = listType
 
- 	updatedEntry.IPOrNet = ipOrNet
 
- 	err = dataprovider.UpdateIPListEntry(&updatedEntry, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderIPListPage(w, r, entry, genericPageModeUpdate, err)
 
- 		return
 
- 	}
 
- 	http.Redirect(w, r, webIPListsPath, http.StatusSeeOther)
 
- }
 
- func (s *httpdServer) handleWebConfigs(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	configs, err := dataprovider.GetConfigs()
 
- 	if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	s.renderConfigsPage(w, r, configs, nil, 0)
 
- }
 
- func (s *httpdServer) handleWebConfigsPost(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	claims, err := getTokenClaims(r)
 
- 	if err != nil || claims.Username == "" {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(errInvalidTokenClaims, util.I18nErrorInvalidToken))
 
- 		return
 
- 	}
 
- 	configs, err := dataprovider.GetConfigs()
 
- 	if err != nil {
 
- 		s.renderInternalServerErrorPage(w, r, err)
 
- 		return
 
- 	}
 
- 	err = r.ParseMultipartForm(maxRequestSize)
 
- 	if err != nil {
 
- 		s.renderBadRequestPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm))
 
- 		return
 
- 	}
 
- 	defer r.MultipartForm.RemoveAll() //nolint:errcheck
 
- 	ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
 
- 	if err := verifyCSRFToken(r, s.csrfTokenAuth); err != nil {
 
- 		s.renderForbiddenPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF))
 
- 		return
 
- 	}
 
- 	var configSection int
 
- 	switch r.Form.Get("form_action") {
 
- 	case "sftp_submit":
 
- 		configSection = 1
 
- 		sftpConfigs := getSFTPConfigsFromPostFields(r)
 
- 		configs.SFTPD = sftpConfigs
 
- 	case "acme_submit":
 
- 		configSection = 2
 
- 		acmeConfigs := getACMEConfigsFromPostFields(r)
 
- 		configs.ACME = acmeConfigs
 
- 		if err := acme.GetCertificatesForConfig(acmeConfigs, configurationDir); err != nil {
 
- 			logger.Info(logSender, "", "unable to get ACME certificates: %v", err)
 
- 			s.renderConfigsPage(w, r, configs, util.NewI18nError(err, util.I18nErrorACMEGeneric), configSection)
 
- 			return
 
- 		}
 
- 	case "smtp_submit":
 
- 		configSection = 3
 
- 		smtpConfigs := getSMTPConfigsFromPostFields(r)
 
- 		updateSMTPSecrets(smtpConfigs, configs.SMTP)
 
- 		configs.SMTP = smtpConfigs
 
- 	case "branding_submit":
 
- 		configSection = 4
 
- 		brandingConfigs, err := getBrandingConfigFromPostFields(r, configs.Branding)
 
- 		if err != nil {
 
- 			logger.Info(logSender, "", "unable to get branding config: %v", err)
 
- 			s.renderConfigsPage(w, r, configs, err, configSection)
 
- 			return
 
- 		}
 
- 		configs.Branding = brandingConfigs
 
- 	default:
 
- 		s.renderBadRequestPage(w, r, errors.New("unsupported form action"))
 
- 		return
 
- 	}
 
- 	err = dataprovider.UpdateConfigs(&configs, claims.Username, ipAddr, claims.Role)
 
- 	if err != nil {
 
- 		s.renderConfigsPage(w, r, configs, err, configSection)
 
- 		return
 
- 	}
 
- 	postConfigsUpdate(configSection, configs)
 
- 	s.renderMessagePage(w, r, util.I18nConfigsTitle, http.StatusOK, nil, util.I18nConfigsOK)
 
- }
 
- func postConfigsUpdate(section int, configs dataprovider.Configs) {
 
- 	switch section {
 
- 	case 3:
 
- 		err := configs.SMTP.TryDecrypt()
 
- 		if err == nil {
 
- 			smtp.Activate(configs.SMTP)
 
- 		} else {
 
- 			logger.Error(logSender, "", "unable to decrypt SMTP configuration, cannot activate configuration: %v", err)
 
- 		}
 
- 	case 4:
 
- 		dbBrandingConfig.Set(configs.Branding)
 
- 	}
 
- }
 
- func (s *httpdServer) handleOAuth2TokenRedirect(w http.ResponseWriter, r *http.Request) {
 
- 	r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
 
- 	stateToken := r.URL.Query().Get("state")
 
- 	state, err := verifyOAuth2Token(s.csrfTokenAuth, stateToken, util.GetIPFromRemoteAddress(r.RemoteAddr))
 
- 	if err != nil {
 
- 		s.renderMessagePage(w, r, util.I18nOAuth2ErrorTitle, http.StatusBadRequest, err, "")
 
- 		return
 
- 	}
 
- 	pendingAuth, err := oauth2Mgr.getPendingAuth(state)
 
- 	if err != nil {
 
- 		oauth2Mgr.removePendingAuth(state)
 
- 		s.renderMessagePage(w, r, util.I18nOAuth2ErrorTitle, http.StatusInternalServerError,
 
- 			util.NewI18nError(err, util.I18nOAuth2ErrorValidateState), "")
 
- 		return
 
- 	}
 
- 	oauth2Mgr.removePendingAuth(state)
 
- 	oauth2Config := smtp.OAuth2Config{
 
- 		Provider:     pendingAuth.Provider,
 
- 		ClientID:     pendingAuth.ClientID,
 
- 		ClientSecret: pendingAuth.ClientSecret.GetPayload(),
 
- 	}
 
- 	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 
- 	defer cancel()
 
- 	cfg := oauth2Config.GetOAuth2()
 
- 	cfg.RedirectURL = pendingAuth.RedirectURL
 
- 	token, err := cfg.Exchange(ctx, r.URL.Query().Get("code"))
 
- 	if err != nil {
 
- 		s.renderMessagePage(w, r, util.I18nOAuth2ErrorTitle, http.StatusInternalServerError,
 
- 			util.NewI18nError(err, util.I18nOAuth2ErrTokenExchange), "")
 
- 		return
 
- 	}
 
- 	if token.RefreshToken == "" {
 
- 		errTxt := "the OAuth2 provider returned an empty token. " +
 
- 			"Some providers only return the token when the user first authorizes. " +
 
- 			"If you have already registered SFTPGo with this user in the past, revoke access and try again. " +
 
- 			"This way you will invalidate the previous token"
 
- 		s.renderMessagePage(w, r, util.I18nOAuth2ErrorTitle, http.StatusBadRequest,
 
- 			util.NewI18nError(errors.New(errTxt), util.I18nOAuth2ErrNoRefreshToken), "")
 
- 		return
 
- 	}
 
- 	s.renderMessagePageWithString(w, r, util.I18nOAuth2Title, http.StatusOK, nil, util.I18nOAuth2OK,
 
- 		fmt.Sprintf("%q", token.RefreshToken))
 
- }
 
- func updateSMTPSecrets(newConfigs, currentConfigs *dataprovider.SMTPConfigs) {
 
- 	if newConfigs.Password.IsNotPlainAndNotEmpty() {
 
- 		newConfigs.Password = currentConfigs.Password
 
- 	}
 
- 	if newConfigs.OAuth2.ClientSecret.IsNotPlainAndNotEmpty() {
 
- 		newConfigs.OAuth2.ClientSecret = currentConfigs.OAuth2.ClientSecret
 
- 	}
 
- 	if newConfigs.OAuth2.RefreshToken.IsNotPlainAndNotEmpty() {
 
- 		newConfigs.OAuth2.RefreshToken = currentConfigs.OAuth2.RefreshToken
 
- 	}
 
- }
 
 
  |