1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742 |
- // Copyright (C) 2019-2022 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_test
- import (
- "bytes"
- "crypto/rand"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/fs"
- "math"
- "mime/multipart"
- "net"
- "net/http"
- "net/http/httptest"
- "net/url"
- "os"
- "path"
- "path/filepath"
- "regexp"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "testing"
- "time"
- "github.com/go-chi/render"
- _ "github.com/go-sql-driver/mysql"
- _ "github.com/jackc/pgx/v5/stdlib"
- "github.com/lithammer/shortuuid/v3"
- _ "github.com/mattn/go-sqlite3"
- "github.com/mhale/smtpd"
- "github.com/pquerna/otp"
- "github.com/pquerna/otp/totp"
- "github.com/rs/xid"
- "github.com/rs/zerolog"
- "github.com/sftpgo/sdk"
- sdkkms "github.com/sftpgo/sdk/kms"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "golang.org/x/crypto/bcrypt"
- "golang.org/x/net/html"
- "github.com/drakkan/sftpgo/v2/internal/common"
- "github.com/drakkan/sftpgo/v2/internal/config"
- "github.com/drakkan/sftpgo/v2/internal/dataprovider"
- "github.com/drakkan/sftpgo/v2/internal/httpclient"
- "github.com/drakkan/sftpgo/v2/internal/httpd"
- "github.com/drakkan/sftpgo/v2/internal/httpdtest"
- "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/sftpd"
- "github.com/drakkan/sftpgo/v2/internal/smtp"
- "github.com/drakkan/sftpgo/v2/internal/util"
- "github.com/drakkan/sftpgo/v2/internal/vfs"
- )
- const (
- defaultUsername = "test_user"
- defaultPassword = "test_password"
- testPubKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC03jj0D+djk7pxIf/0OhrxrchJTRZklofJ1NoIu4752Sq02mdXmarMVsqJ1cAjV5LBVy3D1F5U6XW4rppkXeVtd04Pxb09ehtH0pRRPaoHHlALiJt8CoMpbKYMA8b3KXPPriGxgGomvtU2T2RMURSwOZbMtpsugfjYSWenyYX+VORYhylWnSXL961LTyC21ehd6d6QnW9G7E5hYMITMY9TuQZz3bROYzXiTsgN0+g6Hn7exFQp50p45StUMfV/SftCMdCxlxuyGny2CrN/vfjO7xxOo2uv7q1qm10Q46KPWJQv+pgZ/OfL+EDjy07n5QVSKHlbx+2nT4Q0EgOSQaCTYwn3YjtABfIxWwgAFdyj6YlPulCL22qU4MYhDcA6PSBwDdf8hvxBfvsiHdM+JcSHvv8/VeJhk6CmnZxGY0fxBupov27z3yEO8nAg8k+6PaUiW1MSUfuGMF/ktB8LOstXsEPXSszuyXiOv4DaryOXUiSn7bmRqKcEFlJusO6aZP0= nicola@p1"
- testPubKey1 = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCd60+/j+y8f0tLftihWV1YN9RSahMI9btQMDIMqts/jeNbD8jgoogM3nhF7KxfcaMKURuD47KC4Ey6iAJUJ0sWkSNNxOcIYuvA+5MlspfZDsa8Ag76Fe1vyz72WeHMHMeh/hwFo2TeIeIXg480T1VI6mzfDrVp2GzUx0SS0dMsQBjftXkuVR8YOiOwMCAH2a//M1OrvV7d/NBk6kBN0WnuIBb2jKm15PAA7+jQQG7tzwk2HedNH3jeL5GH31xkSRwlBczRK0xsCQXehAlx6cT/e/s44iJcJTHfpPKoSk6UAhPJYe7Z1QnuoawY9P9jQaxpyeImBZxxUEowhjpj2avBxKdRGBVK8R7EL8tSOeLbhdyWe5Mwc1+foEbq9Zz5j5Kd+hn3Wm1UnsGCrXUUUoZp1jnlNl0NakCto+5KmqnT9cHxaY+ix2RLUWAZyVFlRq71OYux1UHJnEJPiEI1/tr4jFBSL46qhQZv/TfpkfVW8FLz0lErfqu0gQEZnNHr3Fc= nicola@p1"
- defaultTokenAuthUser = "admin"
- defaultTokenAuthPass = "password"
- altAdminUsername = "newTestAdmin"
- altAdminPassword = "password1"
- csrfFormToken = "_form_token"
- tokenPath = "/api/v2/token"
- userTokenPath = "/api/v2/user/token"
- userLogoutPath = "/api/v2/user/logout"
- userPath = "/api/v2/users"
- adminPath = "/api/v2/admins"
- adminPwdPath = "/api/v2/admin/changepwd"
- folderPath = "/api/v2/folders"
- groupPath = "/api/v2/groups"
- activeConnectionsPath = "/api/v2/connections"
- serverStatusPath = "/api/v2/status"
- quotasBasePath = "/api/v2/quotas"
- quotaScanPath = "/api/v2/quotas/users/scans"
- quotaScanVFolderPath = "/api/v2/quotas/folders/scans"
- defenderHosts = "/api/v2/defender/hosts"
- versionPath = "/api/v2/version"
- logoutPath = "/api/v2/logout"
- userPwdPath = "/api/v2/user/changepwd"
- userDirsPath = "/api/v2/user/dirs"
- userFilesPath = "/api/v2/user/files"
- userStreamZipPath = "/api/v2/user/streamzip"
- userUploadFilePath = "/api/v2/user/files/upload"
- userFilesDirsMetadataPath = "/api/v2/user/files/metadata"
- apiKeysPath = "/api/v2/apikeys"
- adminTOTPConfigsPath = "/api/v2/admin/totp/configs"
- adminTOTPGeneratePath = "/api/v2/admin/totp/generate"
- adminTOTPValidatePath = "/api/v2/admin/totp/validate"
- adminTOTPSavePath = "/api/v2/admin/totp/save"
- admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
- adminProfilePath = "/api/v2/admin/profile"
- userTOTPConfigsPath = "/api/v2/user/totp/configs"
- userTOTPGeneratePath = "/api/v2/user/totp/generate"
- userTOTPValidatePath = "/api/v2/user/totp/validate"
- userTOTPSavePath = "/api/v2/user/totp/save"
- user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
- userProfilePath = "/api/v2/user/profile"
- userSharesPath = "/api/v2/user/shares"
- retentionBasePath = "/api/v2/retention/users"
- metadataBasePath = "/api/v2/metadata/users"
- fsEventsPath = "/api/v2/events/fs"
- providerEventsPath = "/api/v2/events/provider"
- sharesPath = "/api/v2/shares"
- eventActionsPath = "/api/v2/eventactions"
- eventRulesPath = "/api/v2/eventrules"
- rolesPath = "/api/v2/roles"
- healthzPath = "/healthz"
- robotsTxtPath = "/robots.txt"
- webBasePath = "/web"
- webBasePathAdmin = "/web/admin"
- webAdminSetupPath = "/web/admin/setup"
- webLoginPath = "/web/admin/login"
- webLogoutPath = "/web/admin/logout"
- webUsersPath = "/web/admin/users"
- webUserPath = "/web/admin/user"
- webGroupsPath = "/web/admin/groups"
- webGroupPath = "/web/admin/group"
- webFoldersPath = "/web/admin/folders"
- webFolderPath = "/web/admin/folder"
- webConnectionsPath = "/web/admin/connections"
- webStatusPath = "/web/admin/status"
- webAdminsPath = "/web/admin/managers"
- webAdminPath = "/web/admin/manager"
- webMaintenancePath = "/web/admin/maintenance"
- webRestorePath = "/web/admin/restore"
- webChangeAdminPwdPath = "/web/admin/changepwd"
- webAdminProfilePath = "/web/admin/profile"
- webTemplateUser = "/web/admin/template/user"
- webTemplateFolder = "/web/admin/template/folder"
- webDefenderPath = "/web/admin/defender"
- webAdminTwoFactorPath = "/web/admin/twofactor"
- webAdminTwoFactorRecoveryPath = "/web/admin/twofactor-recovery"
- webAdminMFAPath = "/web/admin/mfa"
- webAdminTOTPSavePath = "/web/admin/totp/save"
- webAdminForgotPwdPath = "/web/admin/forgot-password"
- webAdminResetPwdPath = "/web/admin/reset-password"
- webAdminEventRulesPath = "/web/admin/eventrules"
- webAdminEventRulePath = "/web/admin/eventrule"
- webAdminEventActionsPath = "/web/admin/eventactions"
- webAdminEventActionPath = "/web/admin/eventaction"
- webAdminRolesPath = "/web/admin/roles"
- webAdminRolePath = "/web/admin/role"
- webEventsPath = "/web/admin/events"
- webBasePathClient = "/web/client"
- webClientLoginPath = "/web/client/login"
- webClientFilesPath = "/web/client/files"
- webClientEditFilePath = "/web/client/editfile"
- webClientDirsPath = "/web/client/dirs"
- webClientDownloadZipPath = "/web/client/downloadzip"
- webChangeClientPwdPath = "/web/client/changepwd"
- webClientProfilePath = "/web/client/profile"
- webClientTwoFactorPath = "/web/client/twofactor"
- webClientTwoFactorRecoveryPath = "/web/client/twofactor-recovery"
- webClientLogoutPath = "/web/client/logout"
- webClientMFAPath = "/web/client/mfa"
- webClientTOTPSavePath = "/web/client/totp/save"
- webClientSharesPath = "/web/client/shares"
- webClientSharePath = "/web/client/share"
- webClientPubSharesPath = "/web/client/pubshares"
- webClientForgotPwdPath = "/web/client/forgot-password"
- webClientResetPwdPath = "/web/client/reset-password"
- webClientViewPDFPath = "/web/client/viewpdf"
- webClientGetPDFPath = "/web/client/getpdf"
- httpBaseURL = "http://127.0.0.1:8081"
- defaultRemoteAddr = "127.0.0.1:1234"
- sftpServerAddr = "127.0.0.1:8022"
- smtpServerAddr = "127.0.0.1:3525"
- httpsCert = `-----BEGIN CERTIFICATE-----
- MIICHTCCAaKgAwIBAgIUHnqw7QnB1Bj9oUsNpdb+ZkFPOxMwCgYIKoZIzj0EAwIw
- RTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
- dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDAyMDQwOTUzMDRaFw0zMDAyMDEw
- OTUzMDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
- VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQA
- IgNiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVqWvrJ51t5OxV0v25NsOgR82CA
- NXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIVCzgWkxiz7XE4lgUwX44FCXZM
- 3+JeUbKjUzBRMB0GA1UdDgQWBBRhLw+/o3+Z02MI/d4tmaMui9W16jAfBgNVHSME
- GDAWgBRhLw+/o3+Z02MI/d4tmaMui9W16jAPBgNVHRMBAf8EBTADAQH/MAoGCCqG
- SM49BAMCA2kAMGYCMQDqLt2lm8mE+tGgtjDmtFgdOcI72HSbRQ74D5rYTzgST1rY
- /8wTi5xl8TiFUyLMUsICMQC5ViVxdXbhuG7gX6yEqSkMKZICHpO8hqFwOD/uaFVI
- dV4vKmHUzwK/eIx+8Ay3neE=
- -----END CERTIFICATE-----`
- httpsKey = `-----BEGIN EC PARAMETERS-----
- BgUrgQQAIg==
- -----END EC PARAMETERS-----
- -----BEGIN EC PRIVATE KEY-----
- MIGkAgEBBDCfMNsN6miEE3rVyUPwElfiJSWaR5huPCzUenZOfJT04GAcQdWvEju3
- UM2lmBLIXpGgBwYFK4EEACKhZANiAARCjRMqJ85rzMC998X5z761nJ+xL3bkmGVq
- WvrJ51t5OxV0v25NsOgR82CANXUgvhVYs7vNFN+jxtb2aj6Xg+/2G/BNxkaFspIV
- CzgWkxiz7XE4lgUwX44FCXZM3+JeUbI=
- -----END EC PRIVATE KEY-----`
- sftpPrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
- b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
- QyNTUxOQAAACB+RB4yNTZz9mHOkawwUibNdemijVV3ErMeLxWUBlCN/gAAAJA7DjpfOw46
- XwAAAAtzc2gtZWQyNTUxOQAAACB+RB4yNTZz9mHOkawwUibNdemijVV3ErMeLxWUBlCN/g
- AAAEA0E24gi8ab/XRSvJ85TGZJMe6HVmwxSG4ExPfTMwwe2n5EHjI1NnP2Yc6RrDBSJs11
- 6aKNVXcSsx4vFZQGUI3+AAAACW5pY29sYUBwMQECAwQ=
- -----END OPENSSH PRIVATE KEY-----`
- sftpPkeyFingerprint = "SHA256:QVQ06XHZZbYZzqfrsZcf3Yozy2WTnqQPeLOkcJCdbP0"
- // password protected private key
- testPrivateKeyPwd = `-----BEGIN OPENSSH PRIVATE KEY-----
- b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAvfwQQcs
- +PyMsCLTNFcKiQAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAILqltfCL7IPuIQ2q
- +8w23flfgskjIlKViEwMfjJR4mrbAAAAkHp5xgG8J1XW90M/fT59ZUQht8sZzzP17rEKlX
- waYKvLzDxkPK6LFIYs55W1EX1eVt/2Maq+zQ7k2SOUmhPNknsUOlPV2gytX3uIYvXF7u2F
- FTBIJuzZ+UQ14wFbraunliE9yye9DajVG1kz2cz2wVgXUbee+gp5NyFVvln+TcTxXwMsWD
- qwlk5iw/jQekxThg==
- -----END OPENSSH PRIVATE KEY-----
- `
- testPubKeyPwd = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILqltfCL7IPuIQ2q+8w23flfgskjIlKViEwMfjJR4mrb"
- privateKeyPwd = "password"
- redactedSecret = "[**redacted**]"
- osWindows = "windows"
- oidcMockAddr = "127.0.0.1:11111"
- )
- var (
- configDir = filepath.Join(".", "..", "..")
- defaultPerms = []string{dataprovider.PermAny}
- homeBasePath string
- backupsPath string
- testServer *httptest.Server
- postConnectPath string
- preActionPath string
- lastResetCode string
- )
- type fakeConnection struct {
- *common.BaseConnection
- command string
- }
- func (c *fakeConnection) Disconnect() error {
- common.Connections.Remove(c.GetID())
- return nil
- }
- func (c *fakeConnection) GetClientVersion() string {
- return ""
- }
- func (c *fakeConnection) GetCommand() string {
- return c.command
- }
- func (c *fakeConnection) GetLocalAddress() string {
- return ""
- }
- func (c *fakeConnection) GetRemoteAddress() string {
- return ""
- }
- type generateTOTPRequest struct {
- ConfigName string `json:"config_name"`
- }
- type generateTOTPResponse struct {
- ConfigName string `json:"config_name"`
- Issuer string `json:"issuer"`
- Secret string `json:"secret"`
- QRCode []byte `json:"qr_code"`
- }
- type validateTOTPRequest struct {
- ConfigName string `json:"config_name"`
- Passcode string `json:"passcode"`
- Secret string `json:"secret"`
- }
- type recoveryCode struct {
- Code string `json:"code"`
- Used bool `json:"used"`
- }
- func TestMain(m *testing.M) {
- homeBasePath = os.TempDir()
- logfilePath := filepath.Join(configDir, "sftpgo_api_test.log")
- logger.InitLogger(logfilePath, 5, 1, 28, false, false, zerolog.DebugLevel)
- os.Setenv("SFTPGO_COMMON__UPLOAD_MODE", "2")
- os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "1")
- os.Setenv("SFTPGO_COMMON__ALLOW_SELF_CONNECTIONS", "1")
- os.Setenv("SFTPGO_DATA_PROVIDER__NAMING_RULES", "0")
- os.Setenv("SFTPGO_DEFAULT_ADMIN_USERNAME", "admin")
- os.Setenv("SFTPGO_DEFAULT_ADMIN_PASSWORD", "password")
- os.Setenv("SFTPGO_HTTPD__BINDINGS__0__WEB_CLIENT_INTEGRATIONS__0__URL", "http://127.0.0.1/test.html")
- os.Setenv("SFTPGO_HTTPD__BINDINGS__0__WEB_CLIENT_INTEGRATIONS__0__FILE_EXTENSIONS", ".pdf,.txt")
- err := config.LoadConfig(configDir, "")
- if err != nil {
- logger.WarnToConsole("error loading configuration: %v", err)
- os.Exit(1)
- }
- wdPath, err := os.Getwd()
- if err != nil {
- logger.WarnToConsole("error getting exe path: %v", err)
- os.Exit(1)
- }
- pluginsConfig := []plugin.Config{
- {
- Type: "eventsearcher",
- Cmd: filepath.Join(wdPath, "..", "..", "tests", "eventsearcher", "eventsearcher"),
- AutoMTLS: true,
- },
- }
- if runtime.GOOS == osWindows {
- pluginsConfig[0].Cmd += ".exe"
- }
- providerConf := config.GetProviderConf()
- logger.InfoToConsole("Starting HTTPD tests, provider: %v", providerConf.Driver)
- err = common.Initialize(config.GetCommonConfig(), 0)
- if err != nil {
- logger.WarnToConsole("error initializing common: %v", err)
- os.Exit(1)
- }
- backupsPath = filepath.Join(os.TempDir(), "test_backups")
- providerConf.BackupsPath = backupsPath
- err = os.MkdirAll(backupsPath, os.ModePerm)
- if err != nil {
- logger.ErrorToConsole("error creating backups path: %v", err)
- os.Exit(1)
- }
- err = dataprovider.Initialize(providerConf, configDir, true)
- if err != nil {
- logger.WarnToConsole("error initializing data provider: %v", err)
- os.Exit(1)
- }
- postConnectPath = filepath.Join(homeBasePath, "postconnect.sh")
- preActionPath = filepath.Join(homeBasePath, "preaction.sh")
- httpConfig := config.GetHTTPConfig()
- httpConfig.RetryMax = 1
- httpConfig.Timeout = 5
- httpConfig.Initialize(configDir) //nolint:errcheck
- kmsConfig := config.GetKMSConfig()
- err = kmsConfig.Initialize()
- if err != nil {
- logger.ErrorToConsole("error initializing kms: %v", err)
- os.Exit(1)
- }
- mfaConfig := config.GetMFAConfig()
- err = mfaConfig.Initialize()
- if err != nil {
- logger.ErrorToConsole("error initializing MFA: %v", err)
- os.Exit(1)
- }
- err = plugin.Initialize(pluginsConfig, "debug")
- if err != nil {
- logger.ErrorToConsole("error initializing plugin: %v", err)
- os.Exit(1)
- }
- httpdConf := config.GetHTTPDConfig()
- httpdConf.Bindings[0].Port = 8081
- httpdConf.Bindings[0].Security = httpd.SecurityConf{
- Enabled: true,
- HTTPSProxyHeaders: []httpd.HTTPSProxyHeader{
- {
- Key: "X-Forwarded-Proto",
- Value: "https",
- },
- },
- }
- httpdtest.SetBaseURL(httpBaseURL)
- // required to test sftpfs
- sftpdConf := config.GetSFTPDConfig()
- sftpdConf.Bindings = []sftpd.Binding{
- {
- Port: 8022,
- },
- }
- hostKeyPath := filepath.Join(os.TempDir(), "id_rsa")
- sftpdConf.HostKeys = []string{hostKeyPath}
- go func() {
- if err := httpdConf.Initialize(configDir, 0); err != nil {
- logger.ErrorToConsole("could not start HTTP server: %v", err)
- os.Exit(1)
- }
- }()
- go func() {
- if err := sftpdConf.Initialize(configDir); err != nil {
- logger.ErrorToConsole("could not start SFTP server: %v", err)
- os.Exit(1)
- }
- }()
- startSMTPServer()
- startOIDCMockServer()
- waitTCPListening(httpdConf.Bindings[0].GetAddress())
- waitTCPListening(sftpdConf.Bindings[0].GetAddress())
- httpd.ReloadCertificateMgr() //nolint:errcheck
- // now start an https server
- certPath := filepath.Join(os.TempDir(), "test.crt")
- keyPath := filepath.Join(os.TempDir(), "test.key")
- err = os.WriteFile(certPath, []byte(httpsCert), os.ModePerm)
- if err != nil {
- logger.ErrorToConsole("error writing HTTPS certificate: %v", err)
- os.Exit(1)
- }
- err = os.WriteFile(keyPath, []byte(httpsKey), os.ModePerm)
- if err != nil {
- logger.ErrorToConsole("error writing HTTPS private key: %v", err)
- os.Exit(1)
- }
- httpdConf.Bindings[0].Port = 8443
- httpdConf.Bindings[0].EnableHTTPS = true
- httpdConf.Bindings[0].CertificateFile = certPath
- httpdConf.Bindings[0].CertificateKeyFile = keyPath
- httpdConf.Bindings = append(httpdConf.Bindings, httpd.Binding{})
- go func() {
- if err := httpdConf.Initialize(configDir, 0); err != nil {
- logger.ErrorToConsole("could not start HTTPS server: %v", err)
- os.Exit(1)
- }
- }()
- waitTCPListening(httpdConf.Bindings[0].GetAddress())
- httpd.ReloadCertificateMgr() //nolint:errcheck
- testServer = httptest.NewServer(httpd.GetHTTPRouter(httpdConf.Bindings[0]))
- defer testServer.Close()
- exitCode := m.Run()
- os.Remove(logfilePath)
- os.RemoveAll(backupsPath)
- os.Remove(certPath)
- os.Remove(keyPath)
- os.Remove(hostKeyPath)
- os.Remove(hostKeyPath + ".pub")
- os.Remove(postConnectPath)
- os.Remove(preActionPath)
- os.Exit(exitCode)
- }
- func TestInitialization(t *testing.T) {
- isShared := 0
- err := config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- invalidFile := "invalid file"
- httpdConf := config.GetHTTPDConfig()
- defaultTemplatesPath := httpdConf.TemplatesPath
- defaultStaticPath := httpdConf.StaticFilesPath
- httpdConf.CertificateFile = invalidFile
- httpdConf.CertificateKeyFile = invalidFile
- err = httpdConf.Initialize(configDir, isShared)
- assert.Error(t, err)
- httpdConf.CertificateFile = ""
- httpdConf.CertificateKeyFile = ""
- httpdConf.TemplatesPath = "."
- err = httpdConf.Initialize(configDir, isShared)
- assert.Error(t, err)
- httpdConf = config.GetHTTPDConfig()
- httpdConf.TemplatesPath = defaultTemplatesPath
- httpdConf.CertificateFile = invalidFile
- httpdConf.CertificateKeyFile = invalidFile
- httpdConf.StaticFilesPath = ""
- httpdConf.TemplatesPath = ""
- err = httpdConf.Initialize(configDir, isShared)
- assert.Error(t, err)
- httpdConf.StaticFilesPath = defaultStaticPath
- httpdConf.TemplatesPath = defaultTemplatesPath
- httpdConf.CertificateFile = filepath.Join(os.TempDir(), "test.crt")
- httpdConf.CertificateKeyFile = filepath.Join(os.TempDir(), "test.key")
- httpdConf.CACertificates = append(httpdConf.CACertificates, invalidFile)
- err = httpdConf.Initialize(configDir, isShared)
- assert.Error(t, err)
- httpdConf.CACertificates = nil
- httpdConf.CARevocationLists = append(httpdConf.CARevocationLists, invalidFile)
- err = httpdConf.Initialize(configDir, isShared)
- assert.Error(t, err)
- httpdConf.CARevocationLists = nil
- httpdConf.Bindings[0].ProxyAllowed = []string{"invalid ip/network"}
- err = httpdConf.Initialize(configDir, isShared)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "is not a valid IP range")
- }
- httpdConf.Bindings[0].ProxyAllowed = nil
- httpdConf.Bindings[0].EnableWebAdmin = false
- httpdConf.Bindings[0].EnableWebClient = false
- httpdConf.Bindings[0].Port = 8081
- httpdConf.Bindings[0].EnableHTTPS = true
- httpdConf.Bindings[0].ClientAuthType = 1
- httpdConf.TokenValidation = 1
- err = httpdConf.Initialize(configDir, 0)
- assert.Error(t, err)
- httpdConf.TokenValidation = 0
- err = httpdConf.Initialize(configDir, 0)
- assert.Error(t, err)
- httpdConf.Bindings[0].OIDC = httpd.OIDC{
- ClientID: "123",
- ClientSecret: "secret",
- ConfigURL: "http://127.0.0.1:11111",
- }
- err = httpdConf.Initialize(configDir, 0)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "oidc")
- }
- httpdConf.Bindings[0].OIDC.UsernameField = "preferred_username"
- err = httpdConf.Initialize(configDir, isShared)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "oidc")
- }
- httpdConf.Bindings[0].OIDC = httpd.OIDC{}
- httpdConf.Bindings[0].EnableWebClient = true
- httpdConf.Bindings[0].EnableWebAdmin = true
- httpdConf.Bindings[0].EnabledLoginMethods = 1
- err = httpdConf.Initialize(configDir, isShared)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "no login method available for WebAdmin UI")
- }
- httpdConf.Bindings[0].EnabledLoginMethods = 2
- err = httpdConf.Initialize(configDir, isShared)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "no login method available for WebAdmin UI")
- }
- httpdConf.Bindings[0].EnabledLoginMethods = 6
- err = httpdConf.Initialize(configDir, isShared)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "no login method available for WebClient UI")
- }
- httpdConf.Bindings[0].EnabledLoginMethods = 4
- err = httpdConf.Initialize(configDir, isShared)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "no login method available for WebClient UI")
- }
- httpdConf.Bindings[0].EnabledLoginMethods = 3
- err = httpdConf.Initialize(configDir, isShared)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "no login method available for WebAdmin UI")
- }
- httpdConf.Bindings[0].EnableWebAdmin = false
- err = httpdConf.Initialize(configDir, isShared)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "no login method available for WebClient UI")
- }
- }
- func TestBasicUserHandling(t *testing.T) {
- u := getTestUser()
- u.Email = "user@user.com"
- user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- lastPwdChange := user.LastPasswordChange
- assert.Greater(t, lastPwdChange, int64(0))
- user.MaxSessions = 10
- user.QuotaSize = 4096
- user.QuotaFiles = 2
- user.UploadBandwidth = 128
- user.DownloadBandwidth = 64
- user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now())
- user.AdditionalInfo = "some free text"
- user.Filters.TLSUsername = sdk.TLSUsernameCN
- user.Email = "user@example.net"
- user.OIDCCustomFields = &map[string]any{
- "field1": "value1",
- }
- user.Filters.WebClient = append(user.Filters.WebClient, sdk.WebClientPubKeyChangeDisabled,
- sdk.WebClientWriteDisabled)
- originalUser := user
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Equal(t, originalUser.ID, user.ID)
- assert.Equal(t, lastPwdChange, user.LastPasswordChange)
- user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.Nil(t, user.OIDCCustomFields)
- user.Email = "invalid@email"
- _, body, err := httpdtest.UpdateUser(user, http.StatusBadRequest, "")
- assert.NoError(t, err)
- assert.Contains(t, string(body), "Validation error: email")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestBasicRoleHandling(t *testing.T) {
- r := getTestRole()
- role, resp, err := httpdtest.AddRole(r, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Greater(t, role.CreatedAt, int64(0))
- assert.Greater(t, role.UpdatedAt, int64(0))
- roleGet, _, err := httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role, roleGet)
- roles, _, err := httpdtest.GetRoles(0, 0, http.StatusOK)
- assert.NoError(t, err)
- if assert.GreaterOrEqual(t, len(roles), 1) {
- found := false
- for _, ro := range roles {
- if ro.Name == r.Name {
- assert.Equal(t, role, ro)
- found = true
- }
- }
- assert.True(t, found)
- }
- roles, _, err = httpdtest.GetRoles(0, int64(len(roles)), http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, roles, 0)
- role.Description = "updated desc"
- _, _, err = httpdtest.UpdateRole(role, http.StatusOK)
- assert.NoError(t, err)
- roleGet, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Description, roleGet.Description)
- _, _, err = httpdtest.GetRoleByName(role.Name+"_", http.StatusNotFound)
- assert.NoError(t, err)
- // adding the same role again should fail
- _, _, err = httpdtest.AddRole(r, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestRoleRelations(t *testing.T) {
- r := getTestRole()
- role, resp, err := httpdtest.AddRole(r, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- a.Role = role.Name
- _, resp, err = httpdtest.AddAdmin(a, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "a role admin cannot have the following permissions")
- a.Permissions = []string{dataprovider.PermAdminAddUsers, dataprovider.PermAdminChangeUsers,
- dataprovider.PermAdminDeleteUsers, dataprovider.PermAdminViewUsers}
- a.Role = "missing admin role"
- _, _, err = httpdtest.AddAdmin(a, http.StatusInternalServerError)
- assert.NoError(t, err)
- a.Role = role.Name
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- admin.Role = "invalid role"
- _, _, err = httpdtest.UpdateAdmin(admin, http.StatusInternalServerError)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Name, admin.Role)
- resp, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.Error(t, err, "removing a referenced role should fail")
- assert.Contains(t, string(resp), "is referenced")
- role, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, role.Admins, 1) {
- assert.Equal(t, admin.Username, role.Admins[0])
- }
- u1 := getTestUser()
- u1.Username = defaultUsername + "1"
- u1.Role = "missing role"
- _, _, err = httpdtest.AddUser(u1, http.StatusInternalServerError)
- assert.NoError(t, err)
- u1.Role = role.Name
- user1, _, err := httpdtest.AddUser(u1, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, role.Name, user1.Role)
- user1.Role = "missing"
- _, _, err = httpdtest.UpdateUser(user1, http.StatusInternalServerError, "")
- assert.NoError(t, err)
- user1, _, err = httpdtest.GetUserByUsername(user1.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Name, user1.Role)
- role, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, role.Admins, 1) {
- assert.Equal(t, admin.Username, role.Admins[0])
- }
- if assert.Len(t, role.Users, 1) {
- assert.Equal(t, user1.Username, role.Users[0])
- }
- roles, _, err := httpdtest.GetRoles(0, 0, http.StatusOK)
- assert.NoError(t, err)
- for _, r := range roles {
- if r.Name == role.Name {
- if assert.Len(t, role.Admins, 1) {
- assert.Equal(t, admin.Username, role.Admins[0])
- }
- if assert.Len(t, role.Users, 1) {
- assert.Equal(t, user1.Username, role.Users[0])
- }
- }
- }
- u2 := getTestUser()
- user2, _, err := httpdtest.AddUser(u2, http.StatusCreated)
- assert.NoError(t, err)
- // the global admin can list all users
- users, _, err := httpdtest.GetUsers(0, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(users), 2)
- _, _, err = httpdtest.GetUserByUsername(user1.Username, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetUserByUsername(user2.Username, http.StatusOK)
- assert.NoError(t, err)
- // the role admin can only list users with its role
- token, _, err := httpdtest.GetToken(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- httpdtest.SetJWTToken(token)
- users, _, err = httpdtest.GetUsers(0, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, users, 1)
- _, _, err = httpdtest.GetUserByUsername(user1.Username, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetUserByUsername(user2.Username, http.StatusNotFound)
- assert.NoError(t, err)
- // the role admin can only update/delete users with its role
- _, _, err = httpdtest.UpdateUser(user1, http.StatusOK, "")
- assert.NoError(t, err)
- _, _, err = httpdtest.UpdateUser(user2, http.StatusNotFound, "")
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user2, http.StatusNotFound)
- assert.NoError(t, err)
- // new users created by a role admin have the same role
- u3 := getTestUser()
- u3.Username = defaultUsername + "3"
- _, _, err = httpdtest.AddUser(u3, http.StatusCreated)
- if assert.Error(t, err) {
- assert.Equal(t, err.Error(), "role mismatch")
- }
- user3, _, err := httpdtest.GetUserByUsername(u3.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Name, user3.Role)
- _, err = httpdtest.RemoveUser(user3, http.StatusOK)
- assert.NoError(t, err)
- httpdtest.SetJWTToken("")
- role, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Admins, []string{altAdminUsername})
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- user1, _, err = httpdtest.GetUserByUsername(user1.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, user1.Role)
- _, err = httpdtest.RemoveUser(user1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user2, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestBasicGroupHandling(t *testing.T) {
- g := getTestGroup()
- group, _, err := httpdtest.AddGroup(g, http.StatusCreated)
- assert.NoError(t, err)
- assert.Greater(t, group.CreatedAt, int64(0))
- assert.Greater(t, group.UpdatedAt, int64(0))
- groupGet, _, err := httpdtest.GetGroupByName(group.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, group, groupGet)
- groups, _, err := httpdtest.GetGroups(0, 0, http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, groups, 1) {
- assert.Equal(t, group, groups[0])
- }
- groups, _, err = httpdtest.GetGroups(0, 1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, groups, 0)
- _, _, err = httpdtest.GetGroupByName(group.Name+"_", http.StatusNotFound)
- assert.NoError(t, err)
- // adding the same group again should fail
- _, _, err = httpdtest.AddGroup(g, http.StatusInternalServerError)
- assert.NoError(t, err)
- group.UserSettings.HomeDir = filepath.Join(os.TempDir(), "%username%")
- group.UserSettings.FsConfig.Provider = sdk.SFTPFilesystemProvider
- group.UserSettings.FsConfig.SFTPConfig.Endpoint = sftpServerAddr
- group.UserSettings.FsConfig.SFTPConfig.Username = defaultUsername
- group.UserSettings.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
- group, _, err = httpdtest.UpdateGroup(group, http.StatusOK)
- assert.NoError(t, err)
- // update again and check that the password was preserved
- dbGroup, err := dataprovider.GroupExists(group.Name)
- assert.NoError(t, err)
- group.UserSettings.FsConfig.SFTPConfig.Password = kms.NewSecret(
- dbGroup.UserSettings.FsConfig.SFTPConfig.Password.GetStatus(),
- dbGroup.UserSettings.FsConfig.SFTPConfig.Password.GetPayload(), "", "")
- group, _, err = httpdtest.UpdateGroup(group, http.StatusOK)
- assert.NoError(t, err)
- dbGroup, err = dataprovider.GroupExists(group.Name)
- assert.NoError(t, err)
- err = dbGroup.UserSettings.FsConfig.SFTPConfig.Password.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, defaultPassword, dbGroup.UserSettings.FsConfig.SFTPConfig.Password.GetPayload())
- group.UserSettings.HomeDir = "relative path"
- _, _, err = httpdtest.UpdateGroup(group, http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.UpdateGroup(group, http.StatusNotFound)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group, http.StatusNotFound)
- assert.NoError(t, err)
- }
- func TestGroupRelations(t *testing.T) {
- mappedPath1 := filepath.Join(os.TempDir(), util.GenerateUniqueID())
- folderName1 := filepath.Base(mappedPath1)
- mappedPath2 := filepath.Join(os.TempDir(), util.GenerateUniqueID())
- folderName2 := filepath.Base(mappedPath2)
- _, _, err := httpdtest.AddFolder(vfs.BaseVirtualFolder{
- Name: folderName2,
- MappedPath: mappedPath2,
- }, http.StatusCreated)
- assert.NoError(t, err)
- g1 := getTestGroup()
- g1.Name += "_1"
- g1.VirtualFolders = append(g1.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName1,
- MappedPath: mappedPath1,
- },
- VirtualPath: "/vdir1",
- })
- g2 := getTestGroup()
- g2.Name += "_2"
- g2.VirtualFolders = append(g2.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName1,
- MappedPath: mappedPath1,
- },
- VirtualPath: "/vdir2",
- })
- g3 := getTestGroup()
- g3.Name += "_3"
- g3.VirtualFolders = append(g3.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName1,
- MappedPath: mappedPath1,
- },
- VirtualPath: "/vdir3",
- })
- group1, resp, err := httpdtest.AddGroup(g1, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Len(t, group1.VirtualFolders, 1)
- group2, resp, err := httpdtest.AddGroup(g2, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Len(t, group2.VirtualFolders, 1)
- group3, resp, err := httpdtest.AddGroup(g3, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Len(t, group3.VirtualFolders, 1)
- folder1, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder1.Groups, 3)
- folder2, _, err := httpdtest.GetFolderByName(folderName2, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder2.Groups, 0)
- group1.VirtualFolders = append(group1.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: folder2,
- VirtualPath: "/vfolder2",
- })
- group1, _, err = httpdtest.UpdateGroup(group1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group1.VirtualFolders, 2)
- folder2, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder2.Groups, 1)
- group1.VirtualFolders = []vfs.VirtualFolder{
- {
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folder1.Name,
- MappedPath: folder1.MappedPath,
- },
- VirtualPath: "/vpathmod",
- },
- }
- group1, _, err = httpdtest.UpdateGroup(group1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group1.VirtualFolders, 1)
- folder2, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder2.Groups, 0)
- group1.VirtualFolders = append(group1.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: folder2,
- VirtualPath: "/vfolder2mod",
- })
- group1, _, err = httpdtest.UpdateGroup(group1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group1.VirtualFolders, 2)
- folder2, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder2.Groups, 1)
- u := getTestUser()
- u.Groups = []sdk.GroupMapping{
- {
- Name: group1.Name,
- Type: sdk.GroupTypePrimary,
- },
- {
- Name: group2.Name,
- Type: sdk.GroupTypeSecondary,
- },
- {
- Name: group3.Name,
- Type: sdk.GroupTypeSecondary,
- },
- }
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- if assert.Len(t, user.Groups, 3) {
- for _, g := range user.Groups {
- if g.Name == group1.Name {
- assert.Equal(t, sdk.GroupTypePrimary, g.Type)
- } else {
- assert.Equal(t, sdk.GroupTypeSecondary, g.Type)
- }
- }
- }
- group1, _, err = httpdtest.GetGroupByName(group1.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group1.Users, 1)
- group2, _, err = httpdtest.GetGroupByName(group2.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group2.Users, 1)
- group3, _, err = httpdtest.GetGroupByName(group3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group3.Users, 1)
- user.Groups = []sdk.GroupMapping{
- {
- Name: group3.Name,
- Type: sdk.GroupTypeSecondary,
- },
- }
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Len(t, user.Groups, 1)
- group1, _, err = httpdtest.GetGroupByName(group1.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group1.Users, 0)
- group2, _, err = httpdtest.GetGroupByName(group2.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group2.Users, 0)
- group3, _, err = httpdtest.GetGroupByName(group3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group3.Users, 1)
- user.Groups = []sdk.GroupMapping{
- {
- Name: group1.Name,
- Type: sdk.GroupTypePrimary,
- },
- {
- Name: group2.Name,
- Type: sdk.GroupTypeSecondary,
- },
- {
- Name: group3.Name,
- Type: sdk.GroupTypeSecondary,
- },
- }
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Len(t, user.Groups, 3)
- group1, _, err = httpdtest.GetGroupByName(group1.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group1.Users, 1)
- group2, _, err = httpdtest.GetGroupByName(group2.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group2.Users, 1)
- group3, _, err = httpdtest.GetGroupByName(group3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group3.Users, 1)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(folder1, http.StatusOK)
- assert.NoError(t, err)
- group1, _, err = httpdtest.GetGroupByName(group1.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group1.Users, 0)
- assert.Len(t, group1.VirtualFolders, 1)
- group2, _, err = httpdtest.GetGroupByName(group2.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group2.Users, 0)
- assert.Len(t, group2.VirtualFolders, 0)
- group3, _, err = httpdtest.GetGroupByName(group3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group3.Users, 0)
- assert.Len(t, group3.VirtualFolders, 0)
- _, err = httpdtest.RemoveGroup(group1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group2, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group3, http.StatusOK)
- assert.NoError(t, err)
- folder2, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder2.Groups, 0)
- _, err = httpdtest.RemoveFolder(folder2, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestGroupValidation(t *testing.T) {
- group := getTestGroup()
- group.VirtualFolders = []vfs.VirtualFolder{
- {
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: util.GenerateUniqueID(),
- MappedPath: filepath.Join(os.TempDir(), util.GenerateUniqueID()),
- },
- },
- }
- _, resp, err := httpdtest.AddGroup(group, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "virtual path is mandatory")
- group.VirtualFolders = nil
- group.UserSettings.FsConfig.Provider = sdk.SFTPFilesystemProvider
- _, resp, err = httpdtest.AddGroup(group, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "endpoint cannot be empty")
- group.UserSettings.FsConfig.Provider = sdk.LocalFilesystemProvider
- group.UserSettings.Permissions = map[string][]string{
- "a": nil,
- }
- _, resp, err = httpdtest.AddGroup(group, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "cannot set permissions for non absolute path")
- group.UserSettings.Permissions = map[string][]string{
- "/": nil,
- }
- _, resp, err = httpdtest.AddGroup(group, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "no permissions granted")
- group.UserSettings.Permissions = map[string][]string{
- "/..": nil,
- }
- _, resp, err = httpdtest.AddGroup(group, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "cannot set permissions for invalid subdirectory")
- group.UserSettings.Permissions = map[string][]string{
- "/": {dataprovider.PermAny},
- }
- group.UserSettings.HomeDir = "relative"
- _, resp, err = httpdtest.AddGroup(group, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "home_dir must be an absolute path")
- group.UserSettings.HomeDir = ""
- group.UserSettings.Filters.WebClient = []string{"invalid permission"}
- _, resp, err = httpdtest.AddGroup(group, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid web client options")
- }
- func TestGroupSettingsOverride(t *testing.T) {
- mappedPath1 := filepath.Join(os.TempDir(), util.GenerateUniqueID())
- folderName1 := filepath.Base(mappedPath1)
- mappedPath2 := filepath.Join(os.TempDir(), util.GenerateUniqueID())
- folderName2 := filepath.Base(mappedPath2)
- g1 := getTestGroup()
- g1.Name += "_1"
- g1.VirtualFolders = append(g1.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName1,
- MappedPath: mappedPath1,
- },
- VirtualPath: "/vdir1",
- })
- g2 := getTestGroup()
- g2.Name += "_2"
- g2.VirtualFolders = append(g2.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName1,
- MappedPath: mappedPath1,
- },
- VirtualPath: "/vdir2",
- })
- g2.VirtualFolders = append(g2.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName2,
- MappedPath: mappedPath2,
- },
- VirtualPath: "/vdir3",
- })
- group1, resp, err := httpdtest.AddGroup(g1, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- group2, resp, err := httpdtest.AddGroup(g2, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- u := getTestUser()
- u.Groups = []sdk.GroupMapping{
- {
- Name: group1.Name,
- Type: sdk.GroupTypePrimary,
- },
- {
- Name: group2.Name,
- Type: sdk.GroupTypeSecondary,
- },
- }
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- assert.Len(t, user.VirtualFolders, 0)
- user, err = dataprovider.CheckUserAndPass(defaultUsername, defaultPassword, "", common.ProtocolHTTP)
- assert.NoError(t, err)
- assert.Len(t, user.VirtualFolders, 3)
- user, err = dataprovider.GetUserAfterIDPAuth(defaultUsername, "", common.ProtocolOIDC, nil)
- assert.NoError(t, err)
- assert.Len(t, user.VirtualFolders, 3)
- user1, user2, err := dataprovider.GetUserVariants(defaultUsername, "")
- assert.NoError(t, err)
- assert.Len(t, user1.VirtualFolders, 0)
- assert.Len(t, user2.VirtualFolders, 3)
- group2.UserSettings.FsConfig = vfs.Filesystem{
- Provider: sdk.SFTPFilesystemProvider,
- SFTPConfig: vfs.SFTPFsConfig{
- BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
- Endpoint: sftpServerAddr,
- Username: defaultUsername,
- },
- Password: kms.NewPlainSecret(defaultPassword),
- },
- }
- group2.UserSettings.Permissions = map[string][]string{
- "/": {dataprovider.PermListItems, dataprovider.PermDownload},
- "/%username%": {dataprovider.PermListItems},
- }
- group2.UserSettings.DownloadBandwidth = 128
- group2.UserSettings.UploadBandwidth = 256
- group2.UserSettings.Filters.WebClient = []string{sdk.WebClientInfoChangeDisabled, sdk.WebClientMFADisabled}
- _, _, err = httpdtest.UpdateGroup(group2, http.StatusOK)
- assert.NoError(t, err)
- user, err = dataprovider.CheckUserAndPass(defaultUsername, defaultPassword, "", common.ProtocolHTTP)
- assert.NoError(t, err)
- assert.Len(t, user.VirtualFolders, 3)
- assert.Equal(t, sdk.LocalFilesystemProvider, user.FsConfig.Provider)
- assert.Equal(t, int64(0), user.DownloadBandwidth)
- assert.Equal(t, int64(0), user.UploadBandwidth)
- assert.Equal(t, []string{dataprovider.PermAny}, user.GetPermissionsForPath("/"))
- assert.Equal(t, []string{dataprovider.PermListItems}, user.GetPermissionsForPath("/"+defaultUsername))
- assert.Len(t, user.Filters.WebClient, 2)
- group1.UserSettings.FsConfig = vfs.Filesystem{
- Provider: sdk.SFTPFilesystemProvider,
- SFTPConfig: vfs.SFTPFsConfig{
- BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
- Endpoint: sftpServerAddr,
- Username: altAdminUsername,
- Prefix: "/dirs/%username%",
- },
- Password: kms.NewPlainSecret(defaultPassword),
- },
- }
- group1.UserSettings.MaxSessions = 2
- group1.UserSettings.QuotaFiles = 1000
- group1.UserSettings.UploadBandwidth = 512
- group1.UserSettings.DownloadBandwidth = 1024
- group1.UserSettings.TotalDataTransfer = 2048
- group1.UserSettings.Filters.MaxUploadFileSize = 1024 * 1024
- group1.UserSettings.Filters.StartDirectory = "/startdir/%username%"
- group1.UserSettings.Filters.WebClient = []string{sdk.WebClientInfoChangeDisabled}
- group1.UserSettings.Permissions = map[string][]string{
- "/": {dataprovider.PermListItems, dataprovider.PermUpload},
- "/sub/%username%": {dataprovider.PermRename},
- "/%username%": {dataprovider.PermDelete},
- }
- group1.UserSettings.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/sub2/%username%test",
- AllowedPatterns: []string{},
- DeniedPatterns: []string{"*.jpg", "*.zip"},
- },
- }
- _, _, err = httpdtest.UpdateGroup(group1, http.StatusOK)
- assert.NoError(t, err)
- user, err = dataprovider.CheckUserAndPass(defaultUsername, defaultPassword, "", common.ProtocolHTTP)
- assert.NoError(t, err)
- assert.Len(t, user.VirtualFolders, 3)
- assert.Equal(t, sdk.SFTPFilesystemProvider, user.FsConfig.Provider)
- assert.Equal(t, altAdminUsername, user.FsConfig.SFTPConfig.Username)
- assert.Equal(t, "/dirs/"+defaultUsername, user.FsConfig.SFTPConfig.Prefix)
- assert.Equal(t, []string{dataprovider.PermListItems, dataprovider.PermUpload}, user.GetPermissionsForPath("/"))
- assert.Equal(t, []string{dataprovider.PermDelete}, user.GetPermissionsForPath("/"+defaultUsername))
- assert.Equal(t, []string{dataprovider.PermRename}, user.GetPermissionsForPath("/sub/"+defaultUsername))
- assert.Equal(t, group1.UserSettings.MaxSessions, user.MaxSessions)
- assert.Equal(t, group1.UserSettings.QuotaFiles, user.QuotaFiles)
- assert.Equal(t, group1.UserSettings.UploadBandwidth, user.UploadBandwidth)
- assert.Equal(t, group1.UserSettings.TotalDataTransfer, user.TotalDataTransfer)
- assert.Equal(t, group1.UserSettings.Filters.MaxUploadFileSize, user.Filters.MaxUploadFileSize)
- assert.Equal(t, "/startdir/"+defaultUsername, user.Filters.StartDirectory)
- if assert.Len(t, user.Filters.FilePatterns, 1) {
- assert.Equal(t, "/sub2/"+defaultUsername+"test", user.Filters.FilePatterns[0].Path)
- }
- if assert.Len(t, user.Filters.WebClient, 2) {
- assert.Contains(t, user.Filters.WebClient, sdk.WebClientInfoChangeDisabled)
- assert.Contains(t, user.Filters.WebClient, sdk.WebClientMFADisabled)
- }
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group2, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestBasicActionRulesHandling(t *testing.T) {
- actionName := "test action"
- a := dataprovider.BaseEventAction{
- Name: actionName,
- Description: "test description",
- Type: dataprovider.ActionTypeBackup,
- Options: dataprovider.BaseEventActionOptions{},
- }
- action, _, err := httpdtest.AddEventAction(a, http.StatusCreated)
- assert.NoError(t, err)
- // adding the same action should fail
- _, _, err = httpdtest.AddEventAction(a, http.StatusInternalServerError)
- assert.NoError(t, err)
- actionGet, _, err := httpdtest.GetEventActionByName(actionName, http.StatusOK)
- assert.NoError(t, err)
- actions, _, err := httpdtest.GetEventActions(0, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.Greater(t, len(actions), 0)
- found := false
- for _, ac := range actions {
- if ac.Name == actionName {
- assert.Equal(t, actionGet, ac)
- found = true
- }
- }
- assert.True(t, found)
- a.Description = "new description"
- a.Type = dataprovider.ActionTypeDataRetentionCheck
- a.Options = dataprovider.BaseEventActionOptions{
- RetentionConfig: dataprovider.EventActionDataRetentionConfig{
- Folders: []dataprovider.FolderRetention{
- {
- Path: "/",
- Retention: 144,
- },
- {
- Path: "/p1",
- Retention: 0,
- },
- {
- Path: "/p2",
- Retention: 12,
- },
- },
- },
- }
- _, _, err = httpdtest.UpdateEventAction(a, http.StatusOK)
- assert.NoError(t, err)
- a.Type = dataprovider.ActionTypeCommand
- a.Options = dataprovider.BaseEventActionOptions{
- CmdConfig: dataprovider.EventActionCommandConfig{
- Cmd: filepath.Join(os.TempDir(), "test_cmd"),
- Timeout: 20,
- EnvVars: []dataprovider.KeyValue{
- {
- Key: "NAME",
- Value: "VALUE",
- },
- },
- },
- }
- _, _, err = httpdtest.UpdateEventAction(a, http.StatusOK)
- assert.NoError(t, err)
- // invalid type
- a.Type = 1000
- _, _, err = httpdtest.UpdateEventAction(a, http.StatusBadRequest)
- assert.NoError(t, err)
- a.Type = dataprovider.ActionTypeEmail
- a.Options = dataprovider.BaseEventActionOptions{
- EmailConfig: dataprovider.EventActionEmailConfig{
- Recipients: []string{"email@example.com"},
- Subject: "Event: {{Event}}",
- Body: "test mail body",
- Attachments: []string{"/{{VirtualPath}}"},
- },
- }
- _, _, err = httpdtest.UpdateEventAction(a, http.StatusOK)
- assert.NoError(t, err)
- a.Type = dataprovider.ActionTypeHTTP
- a.Options = dataprovider.BaseEventActionOptions{
- HTTPConfig: dataprovider.EventActionHTTPConfig{
- Endpoint: "https://localhost:1234",
- Username: defaultUsername,
- Password: kms.NewPlainSecret(defaultPassword),
- Headers: []dataprovider.KeyValue{
- {
- Key: "Content-Type",
- Value: "application/json",
- },
- },
- Timeout: 10,
- SkipTLSVerify: true,
- Method: http.MethodPost,
- QueryParameters: []dataprovider.KeyValue{
- {
- Key: "a",
- Value: "b",
- },
- },
- Body: `{"event":"{{Event}}","name":"{{Name}}"}`,
- },
- }
- action, _, err = httpdtest.UpdateEventAction(a, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, action.Options.HTTPConfig.Password.GetStatus())
- assert.NotEmpty(t, action.Options.HTTPConfig.Password.GetPayload())
- assert.Empty(t, action.Options.HTTPConfig.Password.GetKey())
- assert.Empty(t, action.Options.HTTPConfig.Password.GetAdditionalData())
- // update again and check that the password was preserved
- dbAction, err := dataprovider.EventActionExists(actionName)
- assert.NoError(t, err)
- action.Options.HTTPConfig.Password = kms.NewSecret(
- dbAction.Options.HTTPConfig.Password.GetStatus(),
- dbAction.Options.HTTPConfig.Password.GetPayload(), "", "")
- action, _, err = httpdtest.UpdateEventAction(action, http.StatusOK)
- assert.NoError(t, err)
- dbAction, err = dataprovider.EventActionExists(actionName)
- assert.NoError(t, err)
- err = dbAction.Options.HTTPConfig.Password.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, defaultPassword, dbAction.Options.HTTPConfig.Password.GetPayload())
- r := dataprovider.EventRule{
- Name: "test rule name",
- Description: "",
- Trigger: dataprovider.EventTriggerFsEvent,
- Conditions: dataprovider.EventConditions{
- FsEvents: []string{"upload"},
- Options: dataprovider.ConditionOptions{
- MinFileSize: 1024 * 1024,
- },
- },
- Actions: []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: actionName,
- },
- Order: 1,
- Options: dataprovider.EventActionOptions{
- IsFailureAction: false,
- StopOnFailure: true,
- ExecuteSync: true,
- },
- },
- },
- }
- rule, _, err := httpdtest.AddEventRule(r, http.StatusCreated)
- assert.NoError(t, err)
- // adding the same rule should fail
- _, _, err = httpdtest.AddEventRule(r, http.StatusInternalServerError)
- assert.NoError(t, err)
- rule.Description = "new rule desc"
- rule.Trigger = 1000
- _, _, err = httpdtest.UpdateEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- rule.Trigger = dataprovider.EventTriggerFsEvent
- rule, _, err = httpdtest.UpdateEventRule(rule, http.StatusOK)
- assert.NoError(t, err)
- ruleGet, _, err := httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, ruleGet.Actions, 1) {
- if assert.NotNil(t, ruleGet.Actions[0].BaseEventAction.Options.HTTPConfig.Password) {
- assert.Equal(t, sdkkms.SecretStatusSecretBox, ruleGet.Actions[0].BaseEventAction.Options.HTTPConfig.Password.GetStatus())
- assert.NotEmpty(t, ruleGet.Actions[0].BaseEventAction.Options.HTTPConfig.Password.GetPayload())
- assert.Empty(t, ruleGet.Actions[0].BaseEventAction.Options.HTTPConfig.Password.GetKey())
- assert.Empty(t, ruleGet.Actions[0].BaseEventAction.Options.HTTPConfig.Password.GetAdditionalData())
- }
- }
- rules, _, err := httpdtest.GetEventRules(0, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.Greater(t, len(rules), 0)
- found = false
- for _, ru := range rules {
- if ru.Name == rule.Name {
- assert.Equal(t, ruleGet, ru)
- found = true
- }
- }
- assert.True(t, found)
- _, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.UpdateEventRule(rule, http.StatusNotFound)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusNotFound)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventRule(rule, http.StatusNotFound)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventAction(action, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.UpdateEventAction(action, http.StatusNotFound)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetEventActionByName(actionName, http.StatusNotFound)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventAction(action, http.StatusNotFound)
- assert.NoError(t, err)
- }
- func TestActionRuleRelations(t *testing.T) {
- a1 := dataprovider.BaseEventAction{
- Name: "action1",
- Description: "test description",
- Type: dataprovider.ActionTypeBackup,
- Options: dataprovider.BaseEventActionOptions{},
- }
- a2 := dataprovider.BaseEventAction{
- Name: "action2",
- Type: dataprovider.ActionTypeTransferQuotaReset,
- Options: dataprovider.BaseEventActionOptions{},
- }
- a3 := dataprovider.BaseEventAction{
- Name: "action3",
- Type: dataprovider.ActionTypeEmail,
- Options: dataprovider.BaseEventActionOptions{
- EmailConfig: dataprovider.EventActionEmailConfig{
- Recipients: []string{"test@example.net"},
- Subject: "test subject",
- Body: "test body",
- },
- },
- }
- action1, _, err := httpdtest.AddEventAction(a1, http.StatusCreated)
- assert.NoError(t, err)
- action2, _, err := httpdtest.AddEventAction(a2, http.StatusCreated)
- assert.NoError(t, err)
- action3, _, err := httpdtest.AddEventAction(a3, http.StatusCreated)
- assert.NoError(t, err)
- r1 := dataprovider.EventRule{
- Name: "rule1",
- Description: "",
- Trigger: dataprovider.EventTriggerProviderEvent,
- Conditions: dataprovider.EventConditions{
- ProviderEvents: []string{"add"},
- },
- Actions: []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: action3.Name,
- },
- Order: 2,
- Options: dataprovider.EventActionOptions{
- IsFailureAction: true,
- },
- },
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: action1.Name,
- },
- Order: 1,
- },
- },
- }
- rule1, _, err := httpdtest.AddEventRule(r1, http.StatusCreated)
- assert.NoError(t, err)
- if assert.Len(t, rule1.Actions, 2) {
- assert.Equal(t, action1.Name, rule1.Actions[0].Name)
- assert.Equal(t, 1, rule1.Actions[0].Order)
- assert.Equal(t, action3.Name, rule1.Actions[1].Name)
- assert.Equal(t, 2, rule1.Actions[1].Order)
- assert.True(t, rule1.Actions[1].Options.IsFailureAction)
- }
- r2 := dataprovider.EventRule{
- Name: "rule2",
- Description: "",
- Trigger: dataprovider.EventTriggerSchedule,
- Conditions: dataprovider.EventConditions{
- Schedules: []dataprovider.Schedule{
- {
- Hours: "1",
- DayOfWeek: "*",
- DayOfMonth: "*",
- Month: "*",
- },
- },
- Options: dataprovider.ConditionOptions{
- RoleNames: []dataprovider.ConditionPattern{
- {
- Pattern: "g*",
- InverseMatch: true,
- },
- },
- },
- },
- Actions: []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: action3.Name,
- },
- Order: 2,
- Options: dataprovider.EventActionOptions{
- IsFailureAction: true,
- },
- },
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: action2.Name,
- },
- Order: 1,
- },
- },
- }
- rule2, _, err := httpdtest.AddEventRule(r2, http.StatusCreated)
- assert.NoError(t, err)
- if assert.Len(t, rule1.Actions, 2) {
- assert.Equal(t, action2.Name, rule2.Actions[0].Name)
- assert.Equal(t, 1, rule2.Actions[0].Order)
- assert.Equal(t, action3.Name, rule2.Actions[1].Name)
- assert.Equal(t, 2, rule2.Actions[1].Order)
- assert.True(t, rule2.Actions[1].Options.IsFailureAction)
- }
- // check the references
- action1, _, err = httpdtest.GetEventActionByName(action1.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, action1.Rules, 1)
- assert.True(t, util.Contains(action1.Rules, rule1.Name))
- action2, _, err = httpdtest.GetEventActionByName(action2.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, action2.Rules, 1)
- assert.True(t, util.Contains(action2.Rules, rule2.Name))
- action3, _, err = httpdtest.GetEventActionByName(action3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, action3.Rules, 2)
- assert.True(t, util.Contains(action3.Rules, rule1.Name))
- assert.True(t, util.Contains(action3.Rules, rule2.Name))
- // referenced actions cannot be removed
- _, err = httpdtest.RemoveEventAction(action1, http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventAction(action2, http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventAction(action3, http.StatusBadRequest)
- assert.NoError(t, err)
- // remove action3 from rule2
- r2.Actions = []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: action2.Name,
- },
- Order: 10,
- },
- }
- rule2, _, err = httpdtest.UpdateEventRule(r2, http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, rule2.Actions, 1) {
- assert.Equal(t, action2.Name, rule2.Actions[0].Name)
- assert.Equal(t, 10, rule2.Actions[0].Order)
- }
- // check the updated relation
- action3, _, err = httpdtest.GetEventActionByName(action3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, action3.Rules, 1)
- assert.True(t, util.Contains(action3.Rules, rule1.Name))
- _, err = httpdtest.RemoveEventRule(rule1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventRule(rule2, http.StatusOK)
- assert.NoError(t, err)
- // no relations anymore
- action1, _, err = httpdtest.GetEventActionByName(action1.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, action1.Rules, 0)
- action2, _, err = httpdtest.GetEventActionByName(action2.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, action2.Rules, 0)
- action3, _, err = httpdtest.GetEventActionByName(action3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, action3.Rules, 0)
- _, err = httpdtest.RemoveEventAction(action1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventAction(action2, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventAction(action3, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestEventActionValidation(t *testing.T) {
- action := dataprovider.BaseEventAction{
- Name: "",
- }
- _, resp, err := httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "name is mandatory")
- action = dataprovider.BaseEventAction{
- Name: "n",
- Type: -1,
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid action type")
- action.Type = dataprovider.ActionTypeHTTP
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "HTTP endpoint is required")
- action.Options.HTTPConfig.Endpoint = "abc"
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid HTTP endpoint schema")
- action.Options.HTTPConfig.Endpoint = "http://localhost"
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid HTTP timeout")
- action.Options.HTTPConfig.Timeout = 20
- action.Options.HTTPConfig.Headers = []dataprovider.KeyValue{
- {
- Key: "",
- Value: "",
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid HTTP headers")
- action.Options.HTTPConfig.Headers = []dataprovider.KeyValue{
- {
- Key: "Content-Type",
- Value: "application/json",
- },
- }
- action.Options.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusRedacted, "payload", "", "")
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "cannot save HTTP configuration with a redacted secret")
- action.Options.HTTPConfig.Password = nil
- action.Options.HTTPConfig.Method = http.MethodDelete
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "unsupported HTTP method")
- action.Options.HTTPConfig.Method = http.MethodGet
- action.Options.HTTPConfig.QueryParameters = []dataprovider.KeyValue{
- {
- Key: "a",
- Value: "",
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid HTTP query parameters")
- action.Options.HTTPConfig.QueryParameters = nil
- action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
- {
- Name: "",
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "HTTP part name is required")
- action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
- {
- Name: "p1",
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "HTTP part body is required if no file path is provided")
- action.Options.HTTPConfig.Parts = []dataprovider.HTTPPart{
- {
- Name: "p1",
- Filepath: "p",
- },
- }
- action.Options.HTTPConfig.Body = "b"
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "multipart requests require no body")
- action.Options.HTTPConfig.Body = ""
- action.Options.HTTPConfig.Headers = []dataprovider.KeyValue{
- {
- Key: "Content-Type",
- Value: "application/json",
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "content type is automatically set for multipart requests")
- action.Type = dataprovider.ActionTypeCommand
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "command is required")
- action.Options.CmdConfig.Cmd = "relative"
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid command, it must be an absolute path")
- action.Options.CmdConfig.Cmd = filepath.Join(os.TempDir(), "cmd")
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid command action timeout")
- action.Options.CmdConfig.Timeout = 30
- action.Options.CmdConfig.EnvVars = []dataprovider.KeyValue{
- {
- Key: "k",
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid command env vars")
- action.Options.CmdConfig.EnvVars = nil
- action.Options.CmdConfig.Args = []string{"arg1", ""}
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid command args")
- action.Type = dataprovider.ActionTypeEmail
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "at least one email recipient is required")
- action.Options.EmailConfig.Recipients = []string{""}
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid email recipients")
- action.Options.EmailConfig.Recipients = []string{"a@a.com"}
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "email subject is required")
- action.Options.EmailConfig.Subject = "subject"
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "email body is required")
- action.Type = dataprovider.ActionTypeDataRetentionCheck
- action.Options.RetentionConfig = dataprovider.EventActionDataRetentionConfig{
- Folders: nil,
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "nothing to delete")
- action.Options.RetentionConfig = dataprovider.EventActionDataRetentionConfig{
- Folders: []dataprovider.FolderRetention{
- {
- Path: "/",
- Retention: 0,
- },
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "nothing to delete")
- action.Options.RetentionConfig = dataprovider.EventActionDataRetentionConfig{
- Folders: []dataprovider.FolderRetention{
- {
- Path: "../path",
- Retention: 1,
- },
- {
- Path: "/path",
- Retention: 10,
- },
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "duplicated folder path")
- action.Options.RetentionConfig = dataprovider.EventActionDataRetentionConfig{
- Folders: []dataprovider.FolderRetention{
- {
- Path: "p",
- Retention: -1,
- },
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid folder retention")
- action.Type = dataprovider.ActionTypeFilesystem
- action.Options.FsConfig = dataprovider.EventActionFilesystemConfig{
- Type: dataprovider.FilesystemActionRename,
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "no path to rename specified")
- action.Options.FsConfig.Renames = []dataprovider.KeyValue{
- {
- Key: "",
- Value: "/adir",
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid paths to rename")
- action.Options.FsConfig.Renames = []dataprovider.KeyValue{
- {
- Key: "adir",
- Value: "/adir",
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "rename source and target cannot be equal")
- action.Options.FsConfig.Renames = []dataprovider.KeyValue{
- {
- Key: "/",
- Value: "/dir",
- },
- }
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "renaming the root directory is not allowed")
- action.Options.FsConfig.Type = dataprovider.FilesystemActionMkdirs
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "no directory to create specified")
- action.Options.FsConfig.MkDirs = []string{"dir1", ""}
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid directory to create")
- action.Options.FsConfig.Type = dataprovider.FilesystemActionDelete
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "no path to delete specified")
- action.Options.FsConfig.Deletes = []string{"item1", ""}
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid path to delete")
- action.Options.FsConfig.Type = dataprovider.FilesystemActionExist
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "no path to check for existence specified")
- action.Options.FsConfig.Exist = []string{"item1", ""}
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid path to check for existence")
- action.Options.FsConfig.Type = dataprovider.FilesystemActionCompress
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "archive name is mandatory")
- action.Options.FsConfig.Compress.Name = "archive.zip"
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "no path to compress specified")
- action.Options.FsConfig.Compress.Paths = []string{"item1", ""}
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid path to compress")
- action.Type = dataprovider.ActionTypePasswordExpirationCheck
- action.Options.PwdExpirationConfig.Threshold = 0
- _, resp, err = httpdtest.AddEventAction(action, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "threshold must be greater than 0")
- }
- func TestEventRuleValidation(t *testing.T) {
- rule := dataprovider.EventRule{
- Name: "",
- }
- _, resp, err := httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "name is mandatory")
- rule.Name = "r"
- rule.Trigger = 1000
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid event rule trigger")
- rule.Trigger = dataprovider.EventTriggerFsEvent
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "at least one filesystem event is required")
- rule.Conditions.FsEvents = []string{""}
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "unsupported fs event")
- rule.Conditions.FsEvents = []string{"upload"}
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "at least one action is required")
- rule.Actions = []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "action1",
- },
- Order: 1,
- },
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "",
- },
- },
- }
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "name not specified")
- rule.Actions = []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "action",
- },
- Order: 1,
- },
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "action",
- },
- },
- }
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "duplicated action")
- rule.Actions = []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "action11",
- },
- Order: 1,
- },
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "action12",
- },
- Order: 1,
- },
- }
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "duplicated order")
- rule.Actions = []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "action111",
- },
- Order: 1,
- Options: dataprovider.EventActionOptions{
- IsFailureAction: true,
- },
- },
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "action112",
- },
- Order: 2,
- Options: dataprovider.EventActionOptions{
- IsFailureAction: true,
- },
- },
- }
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "at least a non-failure action is required")
- rule.Actions = []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "action1234",
- },
- Order: 1,
- Options: dataprovider.EventActionOptions{
- IsFailureAction: false,
- },
- },
- }
- rule.Trigger = dataprovider.EventTriggerProviderEvent
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "at least one provider event is required")
- rule.Conditions.ProviderEvents = []string{""}
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "unsupported provider event")
- rule.Conditions.ProviderEvents = []string{"add"}
- rule.Conditions.Options.RoleNames = []dataprovider.ConditionPattern{
- {
- Pattern: "",
- },
- }
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "empty condition pattern not allowed")
- rule.Conditions.Options.RoleNames = nil
- rule.Trigger = dataprovider.EventTriggerSchedule
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "at least one schedule is required")
- rule.Conditions.Schedules = []dataprovider.Schedule{
- {},
- }
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid schedule")
- rule.Conditions.Schedules = []dataprovider.Schedule{
- {
- Hours: "3",
- DayOfWeek: "*",
- DayOfMonth: "*",
- Month: "*",
- },
- }
- _, resp, err = httpdtest.AddEventRule(rule, http.StatusInternalServerError)
- assert.NoError(t, err, string(resp))
- }
- func TestUserTransferLimits(t *testing.T) {
- u := getTestUser()
- u.TotalDataTransfer = 100
- u.Filters.DataTransferLimits = []sdk.DataTransferLimit{
- {
- Sources: nil,
- },
- }
- _, resp, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err, string(resp))
- assert.Contains(t, string(resp), "Validation error: no data transfer limit source specified")
- u.Filters.DataTransferLimits = []sdk.DataTransferLimit{
- {
- Sources: []string{"a"},
- },
- }
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err, string(resp))
- assert.Contains(t, string(resp), "Validation error: could not parse data transfer limit source")
- u.Filters.DataTransferLimits = []sdk.DataTransferLimit{
- {
- Sources: []string{"127.0.0.1/32"},
- UploadDataTransfer: 120,
- DownloadDataTransfer: 140,
- },
- {
- Sources: []string{"192.168.0.0/24", "192.168.1.0/24"},
- TotalDataTransfer: 400,
- },
- {
- Sources: []string{"10.0.0.0/8"},
- },
- }
- user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Len(t, user.Filters.DataTransferLimits, 3)
- assert.Equal(t, u.Filters.DataTransferLimits, user.Filters.DataTransferLimits)
- up, down, total := user.GetDataTransferLimits("1.1.1.1")
- assert.Equal(t, user.TotalDataTransfer*1024*1024, total)
- assert.Equal(t, user.UploadDataTransfer*1024*1024, up)
- assert.Equal(t, user.DownloadDataTransfer*1024*1024, down)
- up, down, total = user.GetDataTransferLimits("127.0.0.1")
- assert.Equal(t, user.Filters.DataTransferLimits[0].TotalDataTransfer*1024*1024, total)
- assert.Equal(t, user.Filters.DataTransferLimits[0].UploadDataTransfer*1024*1024, up)
- assert.Equal(t, user.Filters.DataTransferLimits[0].DownloadDataTransfer*1024*1024, down)
- up, down, total = user.GetDataTransferLimits("192.168.1.6")
- assert.Equal(t, user.Filters.DataTransferLimits[1].TotalDataTransfer*1024*1024, total)
- assert.Equal(t, user.Filters.DataTransferLimits[1].UploadDataTransfer*1024*1024, up)
- assert.Equal(t, user.Filters.DataTransferLimits[1].DownloadDataTransfer*1024*1024, down)
- up, down, total = user.GetDataTransferLimits("10.1.2.3")
- assert.Equal(t, user.Filters.DataTransferLimits[2].TotalDataTransfer*1024*1024, total)
- assert.Equal(t, user.Filters.DataTransferLimits[2].UploadDataTransfer*1024*1024, up)
- assert.Equal(t, user.Filters.DataTransferLimits[2].DownloadDataTransfer*1024*1024, down)
- connID := xid.New().String()
- localAddr := "::1"
- conn := common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "1.1.1.2", user)
- transferQuota := conn.GetTransferQuota()
- assert.Equal(t, user.TotalDataTransfer*1024*1024, transferQuota.AllowedTotalSize)
- assert.Equal(t, user.UploadDataTransfer*1024*1024, transferQuota.AllowedULSize)
- assert.Equal(t, user.DownloadDataTransfer*1024*1024, transferQuota.AllowedDLSize)
- conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "127.0.0.1", user)
- transferQuota = conn.GetTransferQuota()
- assert.Equal(t, user.Filters.DataTransferLimits[0].TotalDataTransfer*1024*1024, transferQuota.AllowedTotalSize)
- assert.Equal(t, user.Filters.DataTransferLimits[0].UploadDataTransfer*1024*1024, transferQuota.AllowedULSize)
- assert.Equal(t, user.Filters.DataTransferLimits[0].DownloadDataTransfer*1024*1024, transferQuota.AllowedDLSize)
- conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "192.168.1.5", user)
- transferQuota = conn.GetTransferQuota()
- assert.Equal(t, user.Filters.DataTransferLimits[1].TotalDataTransfer*1024*1024, transferQuota.AllowedTotalSize)
- assert.Equal(t, user.Filters.DataTransferLimits[1].UploadDataTransfer*1024*1024, transferQuota.AllowedULSize)
- assert.Equal(t, user.Filters.DataTransferLimits[1].DownloadDataTransfer*1024*1024, transferQuota.AllowedDLSize)
- u.UsedDownloadDataTransfer = 10 * 1024 * 1024
- u.UsedUploadDataTransfer = 5 * 1024 * 1024
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "", http.StatusOK)
- assert.NoError(t, err)
- conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "192.168.1.6", user)
- transferQuota = conn.GetTransferQuota()
- assert.Equal(t, (user.Filters.DataTransferLimits[1].TotalDataTransfer-15)*1024*1024, transferQuota.AllowedTotalSize)
- assert.Equal(t, user.Filters.DataTransferLimits[1].UploadDataTransfer*1024*1024, transferQuota.AllowedULSize)
- assert.Equal(t, user.Filters.DataTransferLimits[1].DownloadDataTransfer*1024*1024, transferQuota.AllowedDLSize)
- conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "10.8.3.4", user)
- transferQuota = conn.GetTransferQuota()
- assert.Equal(t, int64(0), transferQuota.AllowedTotalSize)
- assert.Equal(t, int64(0), transferQuota.AllowedULSize)
- assert.Equal(t, int64(0), transferQuota.AllowedDLSize)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserBandwidthLimits(t *testing.T) {
- u := getTestUser()
- u.UploadBandwidth = 128
- u.DownloadBandwidth = 96
- u.Filters.BandwidthLimits = []sdk.BandwidthLimit{
- {
- Sources: []string{"1"},
- },
- }
- _, resp, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err, string(resp))
- assert.Contains(t, string(resp), "Validation error: could not parse bandwidth limit source")
- u.Filters.BandwidthLimits = []sdk.BandwidthLimit{
- {
- Sources: nil,
- },
- }
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err, string(resp))
- assert.Contains(t, string(resp), "Validation error: no bandwidth limit source specified")
- u.Filters.BandwidthLimits = []sdk.BandwidthLimit{
- {
- Sources: []string{"127.0.0.0/8", "::1/128"},
- UploadBandwidth: 256,
- },
- {
- Sources: []string{"10.0.0.0/8"},
- UploadBandwidth: 512,
- DownloadBandwidth: 256,
- },
- }
- user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Len(t, user.Filters.BandwidthLimits, 2)
- assert.Equal(t, u.Filters.BandwidthLimits, user.Filters.BandwidthLimits)
- connID := xid.New().String()
- localAddr := "127.0.0.1"
- up, down := user.GetBandwidthForIP("127.0.1.1", connID)
- assert.Equal(t, int64(256), up)
- assert.Equal(t, int64(0), down)
- conn := common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "127.0.1.1", user)
- assert.Equal(t, int64(256), conn.User.UploadBandwidth)
- assert.Equal(t, int64(0), conn.User.DownloadBandwidth)
- up, down = user.GetBandwidthForIP("10.1.2.3", connID)
- assert.Equal(t, int64(512), up)
- assert.Equal(t, int64(256), down)
- conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "10.2.1.4:1234", user)
- assert.Equal(t, int64(512), conn.User.UploadBandwidth)
- assert.Equal(t, int64(256), conn.User.DownloadBandwidth)
- up, down = user.GetBandwidthForIP("192.168.1.2", connID)
- assert.Equal(t, int64(128), up)
- assert.Equal(t, int64(96), down)
- conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "172.16.0.1", user)
- assert.Equal(t, int64(128), conn.User.UploadBandwidth)
- assert.Equal(t, int64(96), conn.User.DownloadBandwidth)
- up, down = user.GetBandwidthForIP("invalid", connID)
- assert.Equal(t, int64(128), up)
- assert.Equal(t, int64(96), down)
- conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "172.16.0", user)
- assert.Equal(t, int64(128), conn.User.UploadBandwidth)
- assert.Equal(t, int64(96), conn.User.DownloadBandwidth)
- user.Filters.BandwidthLimits = []sdk.BandwidthLimit{
- {
- Sources: []string{"10.0.0.0/24"},
- UploadBandwidth: 256,
- DownloadBandwidth: 512,
- },
- }
- user, resp, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(resp))
- if assert.Len(t, user.Filters.BandwidthLimits, 1) {
- bwLimit := user.Filters.BandwidthLimits[0]
- assert.Equal(t, []string{"10.0.0.0/24"}, bwLimit.Sources)
- assert.Equal(t, int64(256), bwLimit.UploadBandwidth)
- assert.Equal(t, int64(512), bwLimit.DownloadBandwidth)
- }
- up, down = user.GetBandwidthForIP("10.1.2.3", connID)
- assert.Equal(t, int64(128), up)
- assert.Equal(t, int64(96), down)
- conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "172.16.0.2", user)
- assert.Equal(t, int64(128), conn.User.UploadBandwidth)
- assert.Equal(t, int64(96), conn.User.DownloadBandwidth)
- up, down = user.GetBandwidthForIP("10.0.0.26", connID)
- assert.Equal(t, int64(256), up)
- assert.Equal(t, int64(512), down)
- conn = common.NewBaseConnection(connID, common.ProtocolHTTP, localAddr, "10.0.0.28", user)
- assert.Equal(t, int64(256), conn.User.UploadBandwidth)
- assert.Equal(t, int64(512), conn.User.DownloadBandwidth)
- // this works if we remove the omitempty tag from BandwidthLimits
- /*user.Filters.BandwidthLimits = nil
- user, resp, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(resp))
- assert.Len(t, user.Filters.BandwidthLimits, 0)*/
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserTimestamps(t *testing.T) {
- user, resp, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err, string(resp))
- createdAt := user.CreatedAt
- updatedAt := user.UpdatedAt
- assert.Equal(t, int64(0), user.LastLogin)
- assert.Equal(t, int64(0), user.FirstDownload)
- assert.Equal(t, int64(0), user.FirstUpload)
- assert.Greater(t, createdAt, int64(0))
- assert.Greater(t, updatedAt, int64(0))
- mappedPath := filepath.Join(os.TempDir(), "mapped_dir")
- folderName := filepath.Base(mappedPath)
- user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName,
- MappedPath: mappedPath,
- },
- VirtualPath: "/vdir",
- })
- time.Sleep(10 * time.Millisecond)
- user, resp, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(resp))
- assert.Equal(t, int64(0), user.LastLogin)
- assert.Equal(t, int64(0), user.FirstDownload)
- assert.Equal(t, int64(0), user.FirstUpload)
- assert.Equal(t, createdAt, user.CreatedAt)
- assert.Greater(t, user.UpdatedAt, updatedAt)
- updatedAt = user.UpdatedAt
- // after a folder update or delete the user updated_at field should change
- folder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 1)
- time.Sleep(10 * time.Millisecond)
- _, _, err = httpdtest.UpdateFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(0), user.LastLogin)
- assert.Equal(t, int64(0), user.FirstDownload)
- assert.Equal(t, int64(0), user.FirstUpload)
- assert.Equal(t, createdAt, user.CreatedAt)
- assert.Greater(t, user.UpdatedAt, updatedAt)
- updatedAt = user.UpdatedAt
- time.Sleep(10 * time.Millisecond)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(0), user.LastLogin)
- assert.Equal(t, int64(0), user.FirstDownload)
- assert.Equal(t, int64(0), user.FirstUpload)
- assert.Equal(t, createdAt, user.CreatedAt)
- assert.Greater(t, user.UpdatedAt, updatedAt)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAdminTimestamps(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- createdAt := admin.CreatedAt
- updatedAt := admin.UpdatedAt
- assert.Equal(t, int64(0), admin.LastLogin)
- assert.Greater(t, createdAt, int64(0))
- assert.Greater(t, updatedAt, int64(0))
- time.Sleep(10 * time.Millisecond)
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(0), admin.LastLogin)
- assert.Equal(t, createdAt, admin.CreatedAt)
- assert.Greater(t, admin.UpdatedAt, updatedAt)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestHTTPUserAuthEmptyPassword(t *testing.T) {
- u := getTestUser()
- u.Password = ""
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, "")
- c := httpclient.GetHTTPClient()
- resp, err := c.Do(req)
- c.CloseIdleConnections()
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername, "")
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "unexpected status code 401")
- }
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestHTTPAnonymousUser(t *testing.T) {
- u := getTestUser()
- u.Filters.IsAnonymous = true
- _, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.Error(t, err)
- user, _, err := httpdtest.GetUserByUsername(u.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, user.Filters.IsAnonymous)
- assert.Equal(t, []string{dataprovider.PermListItems, dataprovider.PermDownload}, user.Permissions["/"])
- assert.Equal(t, []string{common.ProtocolSSH, common.ProtocolHTTP}, user.Filters.DeniedProtocols)
- assert.Equal(t, []string{dataprovider.SSHLoginMethodPublicKey, dataprovider.SSHLoginMethodPassword,
- dataprovider.SSHLoginMethodKeyboardInteractive, dataprovider.SSHLoginMethodKeyAndPassword,
- dataprovider.SSHLoginMethodKeyAndKeyboardInt, dataprovider.LoginMethodTLSCertificate,
- dataprovider.LoginMethodTLSCertificateAndPwd}, user.Filters.DeniedLoginMethods)
- req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- c := httpclient.GetHTTPClient()
- resp, err := c.Do(req)
- c.CloseIdleConnections()
- assert.NoError(t, err)
- assert.Equal(t, http.StatusForbidden, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "unexpected status code 403")
- }
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestHTTPUserAuthentication(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- c := httpclient.GetHTTPClient()
- resp, err := c.Do(req)
- c.CloseIdleConnections()
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- responseHolder := make(map[string]any)
- err = render.DecodeJSON(resp.Body, &responseHolder)
- assert.NoError(t, err)
- userToken := responseHolder["access_token"].(string)
- assert.NotEmpty(t, userToken)
- err = resp.Body.Close()
- assert.NoError(t, err)
- // login with wrong credentials
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, "")
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, "wrong pwd")
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- respBody, err := io.ReadAll(resp.Body)
- assert.NoError(t, err)
- assert.Contains(t, string(respBody), "invalid credentials")
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth("wrong username", defaultPassword)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- respBody, err = io.ReadAll(resp.Body)
- assert.NoError(t, err)
- assert.Contains(t, string(respBody), "invalid credentials")
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, tokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultTokenAuthUser, defaultTokenAuthPass)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- responseHolder = make(map[string]any)
- err = render.DecodeJSON(resp.Body, &responseHolder)
- assert.NoError(t, err)
- adminToken := responseHolder["access_token"].(string)
- assert.NotEmpty(t, adminToken)
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, versionPath), nil)
- assert.NoError(t, err)
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", adminToken))
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- // using the user token should not work
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, versionPath), nil)
- assert.NoError(t, err)
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", userToken))
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userLogoutPath), nil)
- assert.NoError(t, err)
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", adminToken))
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userLogoutPath), nil)
- assert.NoError(t, err)
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", userToken))
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestPermMFADisabled(t *testing.T) {
- u := getTestUser()
- u.Filters.WebClient = []string{sdk.WebClientMFADisabled}
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- userTOTPConfig := dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- Protocols: []string{common.ProtocolSSH},
- }
- asJSON, err := json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr) // MFA is disabled for this user
- user.Filters.WebClient = []string{sdk.WebClientWriteDisabled}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now we cannot disable MFA for this user
- user.Filters.WebClient = []string{sdk.WebClientMFADisabled}
- _, resp, err := httpdtest.UpdateUser(user, http.StatusBadRequest, "")
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "two-factor authentication cannot be disabled for a user with an active configuration")
- saveReq := make(map[string]bool)
- saveReq["enabled"] = false
- asJSON, err = json.Marshal(saveReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodPost, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestMustChangePasswordRequirement(t *testing.T) {
- u := getTestUser()
- u.Filters.RequirePasswordChange = true
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- assert.True(t, user.Filters.RequirePasswordChange)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, userFilesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Password change required. Please set a new password to continue to use your account")
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- req.RequestURI = webClientFilesPath
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Password change required. Please set a new password to continue to use your account")
- // change pwd
- pwd := make(map[string]string)
- pwd["current_password"] = defaultPassword
- pwd["new_password"] = altAdminPassword
- asJSON, err := json.Marshal(pwd)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userPwdPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // check that the change pwd bool is changed
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.False(t, user.Filters.RequirePasswordChange)
- // get a new token
- token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, altAdminPassword)
- assert.NoError(t, err)
- webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, altAdminPassword)
- assert.NoError(t, err)
- // the new token should work
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- req.RequestURI = webClientFilesPath
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // check the same as above but changing password from the WebClient UI
- user.Filters.RequirePasswordChange = true
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, altAdminPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- req.RequestURI = webClientFilesPath
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("current_password", altAdminPassword)
- form.Set("new_password1", defaultPassword)
- form.Set("new_password2", defaultPassword)
- req, err = http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- req.RequestURI = webClientFilesPath
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestTwoFactorRequirements(t *testing.T) {
- u := getTestUser()
- u.Filters.TwoFactorAuthProtocols = []string{common.ProtocolHTTP, common.ProtocolFTP}
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Two-factor authentication requirements not met, please configure two-factor authentication for the following protocols")
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- req.RequestURI = webClientFilesPath
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Two-factor authentication requirements not met, please configure two-factor authentication for the following protocols")
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
- assert.NoError(t, err)
- userTOTPConfig := dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- Protocols: []string{common.ProtocolHTTP},
- }
- asJSON, err := json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the following protocols are required")
- userTOTPConfig.Protocols = []string{common.ProtocolHTTP, common.ProtocolFTP}
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now get new tokens and check that the two factor requirements are now met
- passcode, err := generateTOTPPasscode(secret)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.Header.Set("X-SFTPGO-OTP", passcode)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- resp, err := httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- responseHolder := make(map[string]any)
- err = render.DecodeJSON(resp.Body, &responseHolder)
- assert.NoError(t, err)
- userToken := responseHolder["access_token"].(string)
- assert.NotEmpty(t, userToken)
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userDirsPath), nil)
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestLoginUserAPITOTP(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- userTOTPConfig := dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- Protocols: []string{common.ProtocolHTTP},
- }
- asJSON, err := json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now require HTTP and SSH for TOTP
- user.Filters.TwoFactorAuthProtocols = []string{common.ProtocolHTTP, common.ProtocolSSH}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- // two factor auth cannot be disabled
- config := make(map[string]any)
- config["enabled"] = false
- asJSON, err = json.Marshal(config)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "two-factor authentication must be enabled")
- // all the required protocols must be enabled
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the following protocols are required")
- // setting all the required protocols should work
- userTOTPConfig.Protocols = []string{common.ProtocolHTTP, common.ProtocolSSH}
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- resp, err := httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- passcode, err := generateTOTPPasscode(secret)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.Header.Set("X-SFTPGO-OTP", passcode)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- responseHolder := make(map[string]any)
- err = render.DecodeJSON(resp.Body, &responseHolder)
- assert.NoError(t, err)
- userToken := responseHolder["access_token"].(string)
- assert.NotEmpty(t, userToken)
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.Header.Set("X-SFTPGO-OTP", passcode)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestLoginAdminAPITOTP(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], admin.Username)
- assert.NoError(t, err)
- altToken, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- adminTOTPConfig := dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- }
- asJSON, err := json.Marshal(adminTOTPConfig)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- assert.Len(t, admin.Filters.RecoveryCodes, 12)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, tokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(altAdminUsername, altAdminPassword)
- resp, err := httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, tokenPath), nil)
- assert.NoError(t, err)
- req.Header.Set("X-SFTPGO-OTP", "passcode")
- req.SetBasicAuth(altAdminUsername, altAdminPassword)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- passcode, err := generateTOTPPasscode(secret)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, tokenPath), nil)
- assert.NoError(t, err)
- req.Header.Set("X-SFTPGO-OTP", passcode)
- req.SetBasicAuth(altAdminUsername, altAdminPassword)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- responseHolder := make(map[string]any)
- err = render.DecodeJSON(resp.Body, &responseHolder)
- assert.NoError(t, err)
- adminToken := responseHolder["access_token"].(string)
- assert.NotEmpty(t, adminToken)
- err = resp.Body.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, versionPath), nil)
- assert.NoError(t, err)
- setBearerForReq(req, adminToken)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- // get/set recovery codes
- req, err = http.NewRequest(http.MethodGet, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodPost, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // disable two-factor auth
- saveReq := make(map[string]bool)
- saveReq["enabled"] = false
- asJSON, err = json.Marshal(saveReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.False(t, admin.Filters.TOTPConfig.Enabled)
- assert.Len(t, admin.Filters.RecoveryCodes, 0)
- // get/set recovery codes will not work
- req, err = http.NewRequest(http.MethodGet, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodPost, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestHTTPStreamZipError(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- resp, err := httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- responseHolder := make(map[string]any)
- err = render.DecodeJSON(resp.Body, &responseHolder)
- assert.NoError(t, err)
- userToken := responseHolder["access_token"].(string)
- assert.NotEmpty(t, userToken)
- err = resp.Body.Close()
- assert.NoError(t, err)
- filesList := []string{"missing"}
- asJSON, err := json.Marshal(filesList)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, fmt.Sprintf("%v%v", httpBaseURL, userStreamZipPath), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", userToken))
- resp, err = httpclient.GetHTTPClient().Do(req)
- if !assert.Error(t, err) { // the connection will be closed
- err = resp.Body.Close()
- assert.NoError(t, err)
- }
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestBasicAdminHandling(t *testing.T) {
- // we have one admin by default
- admins, _, err := httpdtest.GetAdmins(0, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(admins), 1)
- admin := getTestAdmin()
- // the default admin already exists
- _, _, err = httpdtest.AddAdmin(admin, http.StatusInternalServerError)
- assert.NoError(t, err)
- admin.Username = altAdminUsername
- admin.Filters.Preferences.HideUserPageSections = 1 + 4 + 8
- admin.Filters.Preferences.DefaultUsersExpiration = 30
- admin, _, err = httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.Preferences.HideGroups())
- assert.False(t, admin.Filters.Preferences.HideFilesystem())
- assert.True(t, admin.Filters.Preferences.HideVirtualFolders())
- assert.True(t, admin.Filters.Preferences.HideProfile())
- assert.False(t, admin.Filters.Preferences.HideACLs())
- assert.False(t, admin.Filters.Preferences.HideDiskQuotaAndBandwidthLimits())
- assert.False(t, admin.Filters.Preferences.HideAdvancedSettings())
- admin.AdditionalInfo = "test info"
- admin.Filters.Preferences.HideUserPageSections = 16 + 32 + 64
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, "test info", admin.AdditionalInfo)
- assert.False(t, admin.Filters.Preferences.HideGroups())
- assert.False(t, admin.Filters.Preferences.HideFilesystem())
- assert.False(t, admin.Filters.Preferences.HideVirtualFolders())
- assert.False(t, admin.Filters.Preferences.HideProfile())
- assert.True(t, admin.Filters.Preferences.HideACLs())
- assert.True(t, admin.Filters.Preferences.HideDiskQuotaAndBandwidthLimits())
- assert.True(t, admin.Filters.Preferences.HideAdvancedSettings())
- admins, _, err = httpdtest.GetAdmins(1, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, admins, 1)
- assert.NotEqual(t, admin.Username, admins[0].Username)
- admins, _, err = httpdtest.GetAdmins(1, 1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, admins, 1)
- assert.Equal(t, admin.Username, admins[0].Username)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusNotFound)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username+"123", http.StatusNotFound)
- assert.NoError(t, err)
- admin.Username = defaultTokenAuthUser
- _, err = httpdtest.RemoveAdmin(admin, http.StatusBadRequest)
- assert.NoError(t, err)
- }
- func TestAdminGroups(t *testing.T) {
- group1 := getTestGroup()
- group1.Name += "_1"
- group1, _, err := httpdtest.AddGroup(group1, http.StatusCreated)
- assert.NoError(t, err)
- group2 := getTestGroup()
- group2.Name += "_2"
- group2, _, err = httpdtest.AddGroup(group2, http.StatusCreated)
- assert.NoError(t, err)
- group3 := getTestGroup()
- group3.Name += "_3"
- group3, _, err = httpdtest.AddGroup(group3, http.StatusCreated)
- assert.NoError(t, err)
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Groups = []dataprovider.AdminGroupMapping{
- {
- Name: group1.Name,
- Options: dataprovider.AdminGroupMappingOptions{
- AddToUsersAs: dataprovider.GroupAddToUsersAsPrimary,
- },
- },
- {
- Name: group2.Name,
- Options: dataprovider.AdminGroupMappingOptions{
- AddToUsersAs: dataprovider.GroupAddToUsersAsSecondary,
- },
- },
- {
- Name: group3.Name,
- Options: dataprovider.AdminGroupMappingOptions{
- AddToUsersAs: dataprovider.GroupAddToUsersAsMembership,
- },
- },
- }
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- assert.Len(t, admin.Groups, 3)
- groups, _, err := httpdtest.GetGroups(0, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, groups, 3)
- for _, g := range groups {
- if assert.Len(t, g.Admins, 1) {
- assert.Equal(t, admin.Username, g.Admins[0])
- }
- }
- admin, _, err = httpdtest.UpdateAdmin(a, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group2, http.StatusOK)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, admin.Groups, 1)
- // try to add a missing group
- admin.Groups = []dataprovider.AdminGroupMapping{
- {
- Name: group1.Name,
- Options: dataprovider.AdminGroupMappingOptions{
- AddToUsersAs: dataprovider.GroupAddToUsersAsPrimary,
- },
- },
- {
- Name: group2.Name,
- Options: dataprovider.AdminGroupMappingOptions{
- AddToUsersAs: dataprovider.GroupAddToUsersAsSecondary,
- },
- },
- }
- group3, _, err = httpdtest.GetGroupByName(group3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group3.Admins, 1)
- _, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.Error(t, err)
- group3, _, err = httpdtest.GetGroupByName(group3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group3.Admins, 1)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- group3, _, err = httpdtest.GetGroupByName(group3.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group3.Admins, 0)
- _, err = httpdtest.RemoveGroup(group3, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestChangeAdminPassword(t *testing.T) {
- _, err := httpdtest.ChangeAdminPassword("wrong", defaultTokenAuthPass, http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.ChangeAdminPassword(defaultTokenAuthPass, defaultTokenAuthPass, http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.ChangeAdminPassword(defaultTokenAuthPass, defaultTokenAuthPass+"1", http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.ChangeAdminPassword(defaultTokenAuthPass+"1", defaultTokenAuthPass, http.StatusUnauthorized)
- assert.NoError(t, err)
- admin, err := dataprovider.AdminExists(defaultTokenAuthUser)
- assert.NoError(t, err)
- admin.Password = defaultTokenAuthPass
- err = dataprovider.UpdateAdmin(&admin, "", "", "")
- assert.NoError(t, err)
- }
- func TestPasswordValidations(t *testing.T) {
- if config.GetProviderConf().Driver == dataprovider.MemoryDataProviderName {
- t.Skip("this test is not supported with the memory provider")
- }
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- providerConf := config.GetProviderConf()
- assert.NoError(t, err)
- providerConf.PasswordValidation.Admins.MinEntropy = 50
- providerConf.PasswordValidation.Users.MinEntropy = 70
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- _, resp, err := httpdtest.AddAdmin(a, http.StatusBadRequest)
- assert.NoError(t, err, string(resp))
- assert.Contains(t, string(resp), "insecure password")
- _, resp, err = httpdtest.AddUser(getTestUser(), http.StatusBadRequest)
- assert.NoError(t, err, string(resp))
- assert.Contains(t, string(resp), "insecure password")
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- }
- func TestAdminPasswordHashing(t *testing.T) {
- if config.GetProviderConf().Driver == dataprovider.MemoryDataProviderName {
- t.Skip("this test is not supported with the memory provider")
- }
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- providerConf := config.GetProviderConf()
- assert.NoError(t, err)
- providerConf.PasswordHashing.Algo = dataprovider.HashingAlgoArgon2ID
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- currentAdmin, err := dataprovider.AdminExists(defaultTokenAuthUser)
- assert.NoError(t, err)
- assert.True(t, strings.HasPrefix(currentAdmin.Password, "$2a$"))
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- newAdmin, err := dataprovider.AdminExists(altAdminUsername)
- assert.NoError(t, err)
- assert.True(t, strings.HasPrefix(newAdmin.Password, "$argon2id$"))
- token, _, err := httpdtest.GetToken(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- httpdtest.SetJWTToken(token)
- _, _, err = httpdtest.GetStatus(http.StatusOK)
- assert.NoError(t, err)
- httpdtest.SetJWTToken("")
- _, _, err = httpdtest.GetStatus(http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- assert.NoError(t, err)
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- }
- func TestDefaultUsersExpiration(t *testing.T) {
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- a.Filters.Preferences.DefaultUsersExpiration = 30
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- token, _, err := httpdtest.GetToken(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- httpdtest.SetJWTToken(token)
- _, _, err = httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.Error(t, err)
- user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.Greater(t, user.ExpirationDate, int64(0))
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- u := getTestUser()
- u.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Minute))
- _, _, err = httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, u.ExpirationDate, user.ExpirationDate)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- httpdtest.SetJWTToken("")
- _, _, err = httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- // render the user template page
- webToken, err := getJWTWebTokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webTemplateUser, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webTemplateUser+fmt.Sprintf("?from=%s", user.Username), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- httpdtest.SetJWTToken(token)
- _, _, err = httpdtest.AddUser(u, http.StatusNotFound)
- assert.NoError(t, err)
- httpdtest.SetJWTToken("")
- }
- func TestAdminInvalidCredentials(t *testing.T) {
- req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, tokenPath), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultTokenAuthUser, defaultTokenAuthPass)
- resp, err := httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- // wrong password
- req.SetBasicAuth(defaultTokenAuthUser, "wrong pwd")
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- responseHolder := make(map[string]any)
- err = render.DecodeJSON(resp.Body, &responseHolder)
- assert.NoError(t, err)
- err = resp.Body.Close()
- assert.NoError(t, err)
- assert.Equal(t, dataprovider.ErrInvalidCredentials.Error(), responseHolder["error"].(string))
- // wrong username
- req.SetBasicAuth("wrong username", defaultTokenAuthPass)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
- responseHolder = make(map[string]any)
- err = render.DecodeJSON(resp.Body, &responseHolder)
- assert.NoError(t, err)
- err = resp.Body.Close()
- assert.NoError(t, err)
- assert.Equal(t, dataprovider.ErrInvalidCredentials.Error(), responseHolder["error"].(string))
- }
- func TestAdminLastLogin(t *testing.T) {
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, int64(0), admin.LastLogin)
- _, _, err = httpdtest.GetToken(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.Greater(t, admin.LastLogin, int64(0))
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAdminAllowList(t *testing.T) {
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- token, _, err := httpdtest.GetToken(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- httpdtest.SetJWTToken(token)
- _, _, err = httpdtest.GetStatus(http.StatusOK)
- assert.NoError(t, err)
- httpdtest.SetJWTToken("")
- admin.Password = altAdminPassword
- admin.Filters.AllowList = []string{"10.6.6.0/32"}
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetToken(altAdminUsername, altAdminPassword)
- assert.EqualError(t, err, "wrong status code: got 401 want 200")
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserStatus(t *testing.T) {
- u := getTestUser()
- u.Status = 3
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Status = 0
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- user.Status = 2
- _, _, err = httpdtest.UpdateUser(user, http.StatusBadRequest, "")
- assert.NoError(t, err)
- user.Status = 1
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUidGidLimits(t *testing.T) {
- u := getTestUser()
- u.UID = math.MaxInt32
- u.GID = math.MaxInt32
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, math.MaxInt32, user.GetUID())
- assert.Equal(t, math.MaxInt32, user.GetGID())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAddUserNoCredentials(t *testing.T) {
- u := getTestUser()
- u.Password = ""
- u.PublicKeys = []string{}
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- // this user cannot login with an empty password but it still can use an SSH cert
- _, err = getJWTAPITokenFromTestServer(defaultTokenAuthUser, "")
- assert.Error(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAddUserNoUsername(t *testing.T) {
- u := getTestUser()
- u.Username = ""
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- }
- func TestAddUserNoHomeDir(t *testing.T) {
- u := getTestUser()
- u.HomeDir = ""
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- }
- func TestAddUserInvalidHomeDir(t *testing.T) {
- u := getTestUser()
- u.HomeDir = "relative_path" //nolint:goconst
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- }
- func TestAddUserNoPerms(t *testing.T) {
- u := getTestUser()
- u.Permissions = make(map[string][]string)
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Permissions["/"] = []string{}
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- }
- func TestAddUserInvalidEmail(t *testing.T) {
- u := getTestUser()
- u.Email = "invalid_email"
- _, body, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(body), "Validation error: email")
- }
- func TestAddUserInvalidPerms(t *testing.T) {
- u := getTestUser()
- u.Permissions["/"] = []string{"invalidPerm"}
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- // permissions for root dir are mandatory
- u.Permissions["/"] = []string{}
- u.Permissions["/somedir"] = []string{dataprovider.PermAny}
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Permissions["/"] = []string{dataprovider.PermAny}
- u.Permissions["/subdir/.."] = []string{dataprovider.PermAny}
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- }
- func TestAddUserInvalidFilters(t *testing.T) {
- u := getTestUser()
- u.Filters.AllowedIP = []string{"192.168.1.0/24", "192.168.2.0"}
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.AllowedIP = []string{}
- u.Filters.DeniedIP = []string{"192.168.3.0/16", "invalid"}
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.DeniedIP = []string{}
- u.Filters.DeniedLoginMethods = []string{"invalid"}
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.DeniedLoginMethods = dataprovider.ValidLoginMethods
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodTLSCertificateAndPwd}
- u.Filters.DeniedProtocols = dataprovider.ValidProtocols
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.DeniedProtocols = []string{common.ProtocolFTP}
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "relative",
- AllowedPatterns: []string{},
- DeniedPatterns: []string{},
- },
- }
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/",
- AllowedPatterns: []string{},
- DeniedPatterns: []string{},
- },
- }
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/subdir",
- AllowedPatterns: []string{"*.zip"},
- DeniedPatterns: []string{},
- },
- {
- Path: "/subdir",
- AllowedPatterns: []string{"*.rar"},
- DeniedPatterns: []string{"*.jpg"},
- },
- }
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "relative",
- AllowedPatterns: []string{},
- DeniedPatterns: []string{},
- },
- }
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/",
- AllowedPatterns: []string{},
- DeniedPatterns: []string{},
- },
- }
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/subdir",
- AllowedPatterns: []string{"*.zip"},
- },
- {
- Path: "/subdir",
- AllowedPatterns: []string{"*.rar"},
- DeniedPatterns: []string{"*.jpg"},
- },
- }
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/subdir",
- AllowedPatterns: []string{"a\\"},
- },
- }
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/subdir",
- AllowedPatterns: []string{"*.*"},
- DenyPolicy: 100,
- },
- }
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.DeniedProtocols = []string{"invalid"}
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.DeniedProtocols = dataprovider.ValidProtocols
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.DeniedProtocols = nil
- u.Filters.TLSUsername = "not a supported attribute"
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.Filters.TLSUsername = ""
- u.Filters.WebClient = []string{"not a valid web client options"}
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- }
- func TestAddUserInvalidFsConfig(t *testing.T) {
- u := getTestUser()
- u.FsConfig.Provider = sdk.S3FilesystemProvider
- u.FsConfig.S3Config.Bucket = ""
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.S3Config.Bucket = "testbucket"
- u.FsConfig.S3Config.Region = "eu-west-1" //nolint:goconst
- u.FsConfig.S3Config.AccessKey = "access-key" //nolint:goconst
- u.FsConfig.S3Config.AccessSecret = kms.NewSecret(sdkkms.SecretStatusRedacted, "access-secret", "", "")
- u.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?a=b"
- u.FsConfig.S3Config.StorageClass = "Standard" //nolint:goconst
- u.FsConfig.S3Config.KeyPrefix = "/adir/subdir/"
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.S3Config.AccessSecret.SetStatus(sdkkms.SecretStatusPlain)
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.S3Config.KeyPrefix = ""
- u.FsConfig.S3Config.UploadPartSize = 3
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.S3Config.UploadPartSize = 5001
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.S3Config.UploadPartSize = 0
- u.FsConfig.S3Config.UploadConcurrency = -1
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.S3Config.UploadConcurrency = 0
- u.FsConfig.S3Config.DownloadPartSize = -1
- _, resp, err := httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "download_part_size cannot be")
- }
- u.FsConfig.S3Config.DownloadPartSize = 5001
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "download_part_size cannot be")
- }
- u.FsConfig.S3Config.DownloadPartSize = 0
- u.FsConfig.S3Config.DownloadConcurrency = 100
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "invalid download concurrency")
- }
- u.FsConfig.S3Config.DownloadConcurrency = -1
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "invalid download concurrency")
- }
- u.FsConfig.S3Config.DownloadConcurrency = 0
- u.FsConfig.S3Config.Endpoint = ""
- u.FsConfig.S3Config.Region = ""
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "region cannot be empty")
- }
- u = getTestUser()
- u.FsConfig.Provider = sdk.GCSFilesystemProvider
- u.FsConfig.GCSConfig.Bucket = ""
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.GCSConfig.Bucket = "abucket"
- u.FsConfig.GCSConfig.StorageClass = "Standard"
- u.FsConfig.GCSConfig.KeyPrefix = "/somedir/subdir/"
- u.FsConfig.GCSConfig.Credentials = kms.NewSecret(sdkkms.SecretStatusRedacted, "test", "", "") //nolint:goconst
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.GCSConfig.Credentials.SetStatus(sdkkms.SecretStatusPlain)
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.GCSConfig.KeyPrefix = "somedir/subdir/" //nolint:goconst
- u.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
- u.FsConfig.GCSConfig.AutomaticCredentials = 0
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.GCSConfig.Credentials = kms.NewSecret(sdkkms.SecretStatusSecretBox, "invalid", "", "")
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u = getTestUser()
- u.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
- u.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret("http://foo\x7f.com/")
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.AzBlobConfig.SASURL = kms.NewSecret(sdkkms.SecretStatusRedacted, "key", "", "")
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.AzBlobConfig.SASURL = kms.NewEmptySecret()
- u.FsConfig.AzBlobConfig.AccountName = "name"
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.AzBlobConfig.Container = "container"
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.AzBlobConfig.AccountKey = kms.NewSecret(sdkkms.SecretStatusRedacted, "key", "", "")
- u.FsConfig.AzBlobConfig.KeyPrefix = "/amedir/subdir/"
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.AzBlobConfig.AccountKey.SetStatus(sdkkms.SecretStatusPlain)
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.AzBlobConfig.KeyPrefix = "amedir/subdir/"
- u.FsConfig.AzBlobConfig.UploadPartSize = -1
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.AzBlobConfig.UploadPartSize = 101
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u = getTestUser()
- u.FsConfig.Provider = sdk.CryptedFilesystemProvider
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.CryptConfig.Passphrase = kms.NewSecret(sdkkms.SecretStatusRedacted, "akey", "", "")
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u = getTestUser()
- u.FsConfig.Provider = sdk.SFTPFilesystemProvider
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.SFTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusRedacted, "randompkey", "", "")
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.SFTPConfig.Password = kms.NewEmptySecret()
- u.FsConfig.SFTPConfig.PrivateKey = kms.NewSecret(sdkkms.SecretStatusRedacted, "keyforpkey", "", "")
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret("pk")
- u.FsConfig.SFTPConfig.Endpoint = "127.1.1.1:22"
- u.FsConfig.SFTPConfig.Username = defaultUsername
- u.FsConfig.SFTPConfig.BufferSize = -1
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "invalid buffer_size")
- }
- u.FsConfig.SFTPConfig.BufferSize = 1000
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "invalid buffer_size")
- }
- u = getTestUser()
- u.FsConfig.Provider = sdk.HTTPFilesystemProvider
- u.FsConfig.HTTPConfig.Endpoint = "http://foo\x7f.com/"
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "invalid endpoint")
- }
- u.FsConfig.HTTPConfig.Endpoint = "http://127.0.0.1:9999/api/v1"
- u.FsConfig.HTTPConfig.Password = kms.NewSecret(sdkkms.SecretStatusSecretBox, "", "", "")
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "invalid encrypted password")
- }
- u.FsConfig.HTTPConfig.Password = nil
- u.FsConfig.HTTPConfig.APIKey = kms.NewSecret(sdkkms.SecretStatusRedacted, redactedSecret, "", "")
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "cannot save a user with a redacted secret")
- }
- u.FsConfig.HTTPConfig.APIKey = nil
- u.FsConfig.HTTPConfig.Endpoint = "/api/v1"
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "invalid endpoint schema")
- }
- u.FsConfig.HTTPConfig.Endpoint = "http://unix?api_prefix=v1"
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "invalid unix domain socket path")
- }
- u.FsConfig.HTTPConfig.Endpoint = "http://unix?socket_path=test.sock"
- _, resp, err = httpdtest.AddUser(u, http.StatusBadRequest)
- if assert.NoError(t, err) {
- assert.Contains(t, string(resp), "invalid unix domain socket path")
- }
- }
- func TestUserRedactedPassword(t *testing.T) {
- u := getTestUser()
- u.FsConfig.Provider = sdk.S3FilesystemProvider
- u.FsConfig.S3Config.Bucket = "b"
- u.FsConfig.S3Config.Region = "eu-west-1"
- u.FsConfig.S3Config.AccessKey = "access-key"
- u.FsConfig.S3Config.RoleARN = "myRoleARN"
- u.FsConfig.S3Config.AccessSecret = kms.NewSecret(sdkkms.SecretStatusRedacted, "access-secret", "", "")
- u.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?k=m"
- u.FsConfig.S3Config.StorageClass = "Standard"
- u.FsConfig.S3Config.ACL = "bucket-owner-full-control"
- _, resp, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err, string(resp))
- assert.Contains(t, string(resp), "cannot save a user with a redacted secret")
- err = dataprovider.AddUser(&u, "", "", "")
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "cannot save a user with a redacted secret")
- }
- u.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("secret")
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- folderName := "folderName"
- vfolder := vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName,
- MappedPath: filepath.Join(os.TempDir(), "crypted"),
- FsConfig: vfs.Filesystem{
- Provider: sdk.CryptedFilesystemProvider,
- CryptConfig: vfs.CryptFsConfig{
- Passphrase: kms.NewSecret(sdkkms.SecretStatusRedacted, "crypted-secret", "", ""),
- },
- },
- },
- VirtualPath: "/avpath",
- }
- user.Password = defaultPassword
- user.VirtualFolders = append(user.VirtualFolders, vfolder)
- err = dataprovider.UpdateUser(&user, "", "", "")
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "cannot save a user with a redacted secret")
- }
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserType(t *testing.T) {
- u := getTestUser()
- u.Filters.UserType = string(sdk.UserTypeLDAP)
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, string(sdk.UserTypeLDAP), user.Filters.UserType)
- user.Filters.UserType = string(sdk.UserTypeOS)
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Equal(t, string(sdk.UserTypeOS), user.Filters.UserType)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestMetadataAPIMock(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, path.Join(metadataBasePath, "/checks"), nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var resp []any
- err = json.Unmarshal(rr.Body.Bytes(), &resp)
- assert.NoError(t, err)
- assert.Len(t, resp, 0)
- req, err = http.NewRequest(http.MethodPost, path.Join(metadataBasePath, user.Username, "/check"), nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusAccepted, rr)
- assert.Eventually(t, func() bool {
- req, err := http.NewRequest(http.MethodGet, path.Join(metadataBasePath, "/checks"), nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var resp []any
- err = json.Unmarshal(rr.Body.Bytes(), &resp)
- assert.NoError(t, err)
- return len(resp) == 0
- }, 1000*time.Millisecond, 50*time.Millisecond)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(metadataBasePath, user.Username, "/check"), nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestRetentionAPI(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- checks, _, err := httpdtest.GetRetentionChecks(http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, checks, 0)
- localFilePath := filepath.Join(user.HomeDir, "testdir", "testfile")
- err = os.MkdirAll(filepath.Dir(localFilePath), os.ModePerm)
- assert.NoError(t, err)
- err = os.WriteFile(localFilePath, []byte("test data"), os.ModePerm)
- assert.NoError(t, err)
- folderRetention := []dataprovider.FolderRetention{
- {
- Path: "/",
- Retention: 0,
- DeleteEmptyDirs: true,
- },
- }
- _, err = httpdtest.StartRetentionCheck(altAdminUsername, folderRetention, http.StatusNotFound)
- assert.NoError(t, err)
- resp, err := httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "Invalid retention check")
- folderRetention[0].Retention = 24
- _, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
- assert.NoError(t, err)
- assert.Eventually(t, func() bool {
- return len(common.RetentionChecks.Get("")) == 0
- }, 1000*time.Millisecond, 50*time.Millisecond)
- assert.FileExists(t, localFilePath)
- err = os.Chtimes(localFilePath, time.Now().Add(-48*time.Hour), time.Now().Add(-48*time.Hour))
- assert.NoError(t, err)
- _, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusAccepted)
- assert.NoError(t, err)
- assert.Eventually(t, func() bool {
- return len(common.RetentionChecks.Get("")) == 0
- }, 1000*time.Millisecond, 50*time.Millisecond)
- assert.NoFileExists(t, localFilePath)
- assert.NoDirExists(t, filepath.Dir(localFilePath))
- check := common.RetentionCheck{
- Folders: folderRetention,
- }
- c := common.RetentionChecks.Add(check, &user)
- assert.NotNil(t, c)
- _, err = httpdtest.StartRetentionCheck(user.Username, folderRetention, http.StatusConflict)
- assert.NoError(t, err)
- err = c.Start()
- assert.NoError(t, err)
- assert.Len(t, common.RetentionChecks.Get(""), 0)
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin, _, err = httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, retentionBasePath+"/"+user.Username+"/check",
- bytes.NewBuffer([]byte("invalid json")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- asJSON, err := json.Marshal(folderRetention)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPost, retentionBasePath+"/"+user.Username+"/check?notifications=Email,",
- bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "to notify results via email")
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPost, retentionBasePath+"/"+user.Username+"/check?notifications=Email",
- bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestAddUserInvalidVirtualFolders(t *testing.T) {
- u := getTestUser()
- folderName := "fname"
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: filepath.Join(os.TempDir(), "mapped_dir"),
- Name: folderName,
- },
- VirtualPath: "/vdir",
- })
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"),
- Name: folderName + "1",
- },
- VirtualPath: "/vdir", // invalid, already defined
- })
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.VirtualFolders = nil
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: filepath.Join(os.TempDir(), "mapped_dir"),
- Name: folderName,
- },
- VirtualPath: "/vdir1",
- })
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: filepath.Join(os.TempDir(), "mapped_dir"),
- Name: folderName, // invalid, unique constraint (user.id, folder.id) violated
- },
- VirtualPath: "/vdir2",
- })
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.VirtualFolders = nil
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"),
- Name: folderName + "1",
- },
- VirtualPath: "/vdir1/",
- QuotaSize: -1,
- QuotaFiles: 1, // invvalid, we cannot have -1 and > 0
- })
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.VirtualFolders = nil
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"),
- Name: folderName + "1",
- },
- VirtualPath: "/vdir1/",
- QuotaSize: 1,
- QuotaFiles: -1,
- })
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.VirtualFolders = nil
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"),
- Name: folderName + "1",
- },
- VirtualPath: "/vdir1/",
- QuotaSize: -2, // invalid
- QuotaFiles: 0,
- })
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.VirtualFolders = nil
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: filepath.Join(os.TempDir(), "mapped_dir1"),
- Name: folderName + "1",
- },
- VirtualPath: "/vdir1/",
- QuotaSize: 0,
- QuotaFiles: -2, // invalid
- })
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.VirtualFolders = nil
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: filepath.Join(os.TempDir(), "mapped_dir"),
- },
- VirtualPath: "/vdir1",
- })
- // folder name is mandatory
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- }
- func TestUserPublicKey(t *testing.T) {
- u := getTestUser()
- u.Password = ""
- invalidPubKey := "invalid"
- u.PublicKeys = []string{invalidPubKey}
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.PublicKeys = []string{testPubKey}
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- dbUser, err := dataprovider.UserExists(u.Username, "")
- assert.NoError(t, err)
- assert.Empty(t, dbUser.Password)
- assert.False(t, dbUser.IsPasswordHashed())
- user.PublicKeys = []string{testPubKey, invalidPubKey}
- _, _, err = httpdtest.UpdateUser(user, http.StatusBadRequest, "")
- assert.NoError(t, err)
- user.PublicKeys = []string{testPubKey, testPubKey, testPubKey}
- user.Password = defaultPassword
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- dbUser, err = dataprovider.UserExists(u.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, dbUser.Password)
- assert.True(t, dbUser.IsPasswordHashed())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUpdateUserEmptyPassword(t *testing.T) {
- u := getTestUser()
- u.PublicKeys = []string{testPubKey}
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- // the password is not empty
- dbUser, err := dataprovider.UserExists(u.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, dbUser.Password)
- assert.True(t, dbUser.IsPasswordHashed())
- // now update the user and set an empty password
- customUser := make(map[string]any)
- customUser["password"] = ""
- asJSON, err := json.Marshal(customUser)
- assert.NoError(t, err)
- userNoPwd, _, err := httpdtest.UpdateUserWithJSON(user, http.StatusOK, "", asJSON)
- assert.NoError(t, err)
- assert.Equal(t, user.Password, userNoPwd.Password) // the password is hidden
- // check the password within the data provider
- dbUser, err = dataprovider.UserExists(u.Username, "")
- assert.NoError(t, err)
- assert.Empty(t, dbUser.Password)
- assert.False(t, dbUser.IsPasswordHashed())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUpdateUser(t *testing.T) {
- u := getTestUser()
- u.UsedQuotaFiles = 1
- u.UsedQuotaSize = 2
- u.Filters.TLSUsername = sdk.TLSUsernameCN
- u.Filters.Hooks.CheckPasswordDisabled = true
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, 0, user.UsedQuotaFiles)
- assert.Equal(t, int64(0), user.UsedQuotaSize)
- user.HomeDir = filepath.Join(homeBasePath, "testmod")
- user.UID = 33
- user.GID = 101
- user.MaxSessions = 10
- user.QuotaSize = 4096
- user.QuotaFiles = 2
- user.Permissions["/"] = []string{dataprovider.PermCreateDirs, dataprovider.PermDelete, dataprovider.PermDownload}
- user.Permissions["/subdir"] = []string{dataprovider.PermListItems, dataprovider.PermUpload}
- user.Filters.AllowedIP = []string{"192.168.1.0/24", "192.168.2.0/24"}
- user.Filters.DeniedIP = []string{"192.168.3.0/24", "192.168.4.0/24"}
- user.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
- user.Filters.DeniedProtocols = []string{common.ProtocolWebDAV}
- user.Filters.TLSUsername = sdk.TLSUsernameNone
- user.Filters.Hooks.ExternalAuthDisabled = true
- user.Filters.Hooks.PreLoginDisabled = true
- user.Filters.Hooks.CheckPasswordDisabled = false
- user.Filters.DisableFsChecks = true
- user.Filters.FilePatterns = append(user.Filters.FilePatterns, sdk.PatternsFilter{
- Path: "/subdir",
- AllowedPatterns: []string{"*.zip", "*.rar"},
- DeniedPatterns: []string{"*.jpg", "*.png"},
- DenyPolicy: sdk.DenyPolicyHide,
- })
- user.Filters.MaxUploadFileSize = 4096
- user.UploadBandwidth = 1024
- user.DownloadBandwidth = 512
- user.VirtualFolders = nil
- mappedPath1 := filepath.Join(os.TempDir(), "mapped_dir1")
- mappedPath2 := filepath.Join(os.TempDir(), "mapped_dir2")
- folderName1 := filepath.Base(mappedPath1)
- folderName2 := filepath.Base(mappedPath2)
- user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName1,
- MappedPath: mappedPath1,
- },
- VirtualPath: "/vdir1",
- })
- user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName2,
- MappedPath: mappedPath2,
- },
- VirtualPath: "/vdir12/subdir",
- QuotaSize: 123,
- QuotaFiles: 2,
- })
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- _, _, err = httpdtest.UpdateUser(user, http.StatusBadRequest, "invalid")
- assert.NoError(t, err)
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "0")
- assert.NoError(t, err)
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "1")
- assert.NoError(t, err)
- user.Permissions["/subdir"] = []string{}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Len(t, user.Permissions["/subdir"], 0)
- assert.Len(t, user.VirtualFolders, 2)
- for _, folder := range user.VirtualFolders {
- assert.Greater(t, folder.ID, int64(0))
- if folder.VirtualPath == "/vdir12/subdir" {
- assert.Equal(t, int64(123), folder.QuotaSize)
- assert.Equal(t, 2, folder.QuotaFiles)
- }
- }
- folder, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 1)
- assert.Contains(t, folder.Users, user.Username)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- // removing the user must remove folder mapping
- folder, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 0)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- folder, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 0)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUpdateUserTransferQuotaUsage(t *testing.T) {
- u := getTestUser()
- usedDownloadDataTransfer := int64(2 * 1024 * 1024)
- usedUploadDataTransfer := int64(1024 * 1024)
- u.UsedDownloadDataTransfer = usedDownloadDataTransfer
- u.UsedUploadDataTransfer = usedUploadDataTransfer
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(0), user.UsedUploadDataTransfer)
- assert.Equal(t, int64(0), user.UsedDownloadDataTransfer)
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "invalid_mode", http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, usedUploadDataTransfer, user.UsedUploadDataTransfer)
- assert.Equal(t, usedDownloadDataTransfer, user.UsedDownloadDataTransfer)
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "add", http.StatusBadRequest)
- assert.NoError(t, err, "user has no transfer quota restrictions add mode should fail")
- user.TotalDataTransfer = 100
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "add", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 2*usedUploadDataTransfer, user.UsedUploadDataTransfer)
- assert.Equal(t, 2*usedDownloadDataTransfer, user.UsedDownloadDataTransfer)
- u.UsedDownloadDataTransfer = -1
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "add", http.StatusBadRequest)
- assert.NoError(t, err)
- u.UsedDownloadDataTransfer = usedDownloadDataTransfer
- u.Username += "1"
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "", http.StatusNotFound)
- assert.NoError(t, err)
- u.Username = defaultUsername
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, usedUploadDataTransfer, user.UsedUploadDataTransfer)
- assert.Equal(t, usedDownloadDataTransfer, user.UsedDownloadDataTransfer)
- u.UsedDownloadDataTransfer = 0
- u.UsedUploadDataTransfer = 1
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "add", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, usedUploadDataTransfer+1, user.UsedUploadDataTransfer)
- assert.Equal(t, usedDownloadDataTransfer, user.UsedDownloadDataTransfer)
- u.UsedDownloadDataTransfer = 1
- u.UsedUploadDataTransfer = 0
- _, err = httpdtest.UpdateTransferQuotaUsage(u, "add", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, usedUploadDataTransfer+1, user.UsedUploadDataTransfer)
- assert.Equal(t, usedDownloadDataTransfer+1, user.UsedDownloadDataTransfer)
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "users", u.Username, "transfer-usage"),
- bytes.NewBuffer([]byte(`not a json`)))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUpdateUserQuotaUsage(t *testing.T) {
- u := getTestUser()
- usedQuotaFiles := 1
- usedQuotaSize := int64(65535)
- u.UsedQuotaFiles = usedQuotaFiles
- u.UsedQuotaSize = usedQuotaSize
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 0, user.UsedQuotaFiles)
- assert.Equal(t, int64(0), user.UsedQuotaSize)
- _, err = httpdtest.UpdateQuotaUsage(u, "invalid_mode", http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.UpdateQuotaUsage(u, "", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, usedQuotaFiles, user.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize, user.UsedQuotaSize)
- _, err = httpdtest.UpdateQuotaUsage(u, "add", http.StatusBadRequest)
- assert.NoError(t, err, "user has no quota restrictions add mode should fail")
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, usedQuotaFiles, user.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize, user.UsedQuotaSize)
- user.QuotaFiles = 100
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- _, err = httpdtest.UpdateQuotaUsage(u, "add", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 2*usedQuotaFiles, user.UsedQuotaFiles)
- assert.Equal(t, 2*usedQuotaSize, user.UsedQuotaSize)
- u.UsedQuotaFiles = -1
- _, err = httpdtest.UpdateQuotaUsage(u, "", http.StatusBadRequest)
- assert.NoError(t, err)
- u.UsedQuotaFiles = usedQuotaFiles
- u.Username = u.Username + "1"
- _, err = httpdtest.UpdateQuotaUsage(u, "", http.StatusNotFound)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserFolderMapping(t *testing.T) {
- mappedPath1 := filepath.Join(os.TempDir(), "mapped_dir1")
- mappedPath2 := filepath.Join(os.TempDir(), "mapped_dir2")
- folderName1 := filepath.Base(mappedPath1)
- folderName2 := filepath.Base(mappedPath2)
- u1 := getTestUser()
- u1.VirtualFolders = append(u1.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName1,
- MappedPath: mappedPath1,
- UsedQuotaFiles: 2,
- UsedQuotaSize: 123,
- LastQuotaUpdate: 456,
- },
- VirtualPath: "/vdir",
- QuotaSize: -1,
- QuotaFiles: -1,
- })
- user1, _, err := httpdtest.AddUser(u1, http.StatusCreated)
- assert.NoError(t, err)
- // virtual folder must be auto created
- folder, _, err := httpdtest.GetFolderByName(folderName1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 1)
- assert.Contains(t, folder.Users, user1.Username)
- assert.Equal(t, 0, folder.UsedQuotaFiles)
- assert.Equal(t, int64(0), folder.UsedQuotaSize)
- assert.Equal(t, int64(0), folder.LastQuotaUpdate)
- assert.Equal(t, 0, user1.VirtualFolders[0].UsedQuotaFiles)
- assert.Equal(t, int64(0), user1.VirtualFolders[0].UsedQuotaSize)
- assert.Equal(t, int64(0), user1.VirtualFolders[0].LastQuotaUpdate)
- u2 := getTestUser()
- u2.Username = defaultUsername + "2"
- u2.VirtualFolders = append(u2.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName1,
- MappedPath: mappedPath1,
- },
- VirtualPath: "/vdir1",
- QuotaSize: 0,
- QuotaFiles: 0,
- })
- u2.VirtualFolders = append(u2.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName2,
- MappedPath: mappedPath2,
- },
- VirtualPath: "/vdir2",
- QuotaSize: -1,
- QuotaFiles: -1,
- })
- user2, _, err := httpdtest.AddUser(u2, http.StatusCreated)
- assert.NoError(t, err)
- folder, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 1)
- assert.Contains(t, folder.Users, user2.Username)
- folder, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 2)
- assert.Contains(t, folder.Users, user1.Username)
- assert.Contains(t, folder.Users, user2.Username)
- // now update user2 removing mappedPath1
- user2.VirtualFolders = nil
- user2.VirtualFolders = append(user2.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName2,
- MappedPath: mappedPath2,
- UsedQuotaFiles: 2,
- UsedQuotaSize: 123,
- },
- VirtualPath: "/vdir",
- QuotaSize: 0,
- QuotaFiles: 0,
- })
- user2, _, err = httpdtest.UpdateUser(user2, http.StatusOK, "")
- assert.NoError(t, err)
- folder, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 1)
- assert.Contains(t, folder.Users, user2.Username)
- assert.Equal(t, 0, folder.UsedQuotaFiles)
- assert.Equal(t, int64(0), folder.UsedQuotaSize)
- folder, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 1)
- assert.Contains(t, folder.Users, user1.Username)
- // add mappedPath1 again to user2
- user2.VirtualFolders = append(user2.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName1,
- MappedPath: mappedPath1,
- },
- VirtualPath: "/vdir1",
- })
- user2, _, err = httpdtest.UpdateUser(user2, http.StatusOK, "")
- assert.NoError(t, err)
- folder, _, err = httpdtest.GetFolderByName(folderName2, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 1)
- assert.Contains(t, folder.Users, user2.Username)
- // removing virtual folders should clear relations on both side
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName2}, http.StatusOK)
- assert.NoError(t, err)
- user2, _, err = httpdtest.GetUserByUsername(user2.Username, http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, user2.VirtualFolders, 1) {
- folder := user2.VirtualFolders[0]
- assert.Equal(t, mappedPath1, folder.MappedPath)
- assert.Equal(t, folderName1, folder.Name)
- }
- user1, _, err = httpdtest.GetUserByUsername(user1.Username, http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, user2.VirtualFolders, 1) {
- folder := user2.VirtualFolders[0]
- assert.Equal(t, mappedPath1, folder.MappedPath)
- }
- folder, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 2)
- // removing a user should clear virtual folder mapping
- _, err = httpdtest.RemoveUser(user1, http.StatusOK)
- assert.NoError(t, err)
- folder, _, err = httpdtest.GetFolderByName(folderName1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folder.Users, 1)
- assert.Contains(t, folder.Users, user2.Username)
- // removing a folder should clear mapping on the user side too
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName1}, http.StatusOK)
- assert.NoError(t, err)
- user2, _, err = httpdtest.GetUserByUsername(user2.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, user2.VirtualFolders, 0)
- _, err = httpdtest.RemoveUser(user2, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserS3Config(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.S3FilesystemProvider
- user.FsConfig.S3Config.Bucket = "test" //nolint:goconst
- user.FsConfig.S3Config.AccessKey = "Server-Access-Key"
- user.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("Server-Access-Secret")
- user.FsConfig.S3Config.RoleARN = "myRoleARN"
- user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000"
- user.FsConfig.S3Config.UploadPartSize = 8
- user.FsConfig.S3Config.DownloadPartMaxTime = 60
- user.FsConfig.S3Config.UploadPartMaxTime = 40
- user.FsConfig.S3Config.ForcePathStyle = true
- user.FsConfig.S3Config.DownloadPartSize = 6
- folderName := "vfolderName"
- user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName,
- MappedPath: filepath.Join(os.TempDir(), "folderName"),
- FsConfig: vfs.Filesystem{
- Provider: sdk.CryptedFilesystemProvider,
- CryptConfig: vfs.CryptFsConfig{
- Passphrase: kms.NewPlainSecret("Crypted-Secret"),
- },
- },
- },
- VirtualPath: "/folderPath",
- })
- user, body, err := httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(body))
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.NotEmpty(t, user.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetKey())
- assert.Equal(t, 60, user.FsConfig.S3Config.DownloadPartMaxTime)
- assert.Equal(t, 40, user.FsConfig.S3Config.UploadPartMaxTime)
- if assert.Len(t, user.VirtualFolders, 1) {
- folder := user.VirtualFolders[0]
- assert.Equal(t, sdkkms.SecretStatusSecretBox, folder.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, folder.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, folder.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.Empty(t, folder.FsConfig.CryptConfig.Passphrase.GetKey())
- }
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- folder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, folder.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, folder.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, folder.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.Empty(t, folder.FsConfig.CryptConfig.Passphrase.GetKey())
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- user.Password = defaultPassword
- user.ID = 0
- user.CreatedAt = 0
- user.VirtualFolders = nil
- secret := kms.NewSecret(sdkkms.SecretStatusSecretBox, "Server-Access-Secret", "", "")
- user.FsConfig.S3Config.AccessSecret = secret
- _, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.Error(t, err)
- user.FsConfig.S3Config.AccessSecret.SetStatus(sdkkms.SecretStatusPlain)
- user, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.NoError(t, err)
- initialSecretPayload := user.FsConfig.S3Config.AccessSecret.GetPayload()
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.NotEmpty(t, initialSecretPayload)
- assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetKey())
- user.FsConfig.Provider = sdk.S3FilesystemProvider
- user.FsConfig.S3Config.Bucket = "test-bucket"
- user.FsConfig.S3Config.Region = "us-east-1" //nolint:goconst
- user.FsConfig.S3Config.AccessKey = "Server-Access-Key1"
- user.FsConfig.S3Config.Endpoint = "http://localhost:9000"
- user.FsConfig.S3Config.KeyPrefix = "somedir/subdir" //nolint:goconst
- user.FsConfig.S3Config.UploadConcurrency = 5
- user.FsConfig.S3Config.DownloadConcurrency = 4
- user, bb, err := httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(bb))
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.Equal(t, initialSecretPayload, user.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- assert.Empty(t, user.FsConfig.S3Config.AccessSecret.GetKey())
- // test user without access key and access secret (shared config state)
- user.FsConfig.Provider = sdk.S3FilesystemProvider
- user.FsConfig.S3Config.Bucket = "testbucket"
- user.FsConfig.S3Config.Region = "us-east-1"
- user.FsConfig.S3Config.AccessKey = ""
- user.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
- user.FsConfig.S3Config.Endpoint = ""
- user.FsConfig.S3Config.KeyPrefix = "somedir/subdir"
- user.FsConfig.S3Config.UploadPartSize = 6
- user.FsConfig.S3Config.UploadConcurrency = 4
- user, body, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(body))
- assert.Nil(t, user.FsConfig.S3Config.AccessSecret)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- user.Password = defaultPassword
- user.ID = 0
- user.CreatedAt = 0
- // shared credential test for add instead of update
- user, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.NoError(t, err)
- assert.Nil(t, user.FsConfig.S3Config.AccessSecret)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestHTTPFsConfig(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.HTTPFilesystemProvider
- user.FsConfig.HTTPConfig = vfs.HTTPFsConfig{
- BaseHTTPFsConfig: sdk.BaseHTTPFsConfig{
- Endpoint: "http://127.0.0.1/httpfs",
- Username: defaultUsername,
- },
- Password: kms.NewPlainSecret(defaultPassword),
- APIKey: kms.NewPlainSecret(defaultTokenAuthUser),
- }
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- initialPwdPayload := user.FsConfig.HTTPConfig.Password.GetPayload()
- initialAPIKeyPayload := user.FsConfig.HTTPConfig.APIKey.GetPayload()
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.HTTPConfig.Password.GetStatus())
- assert.NotEmpty(t, initialPwdPayload)
- assert.Empty(t, user.FsConfig.HTTPConfig.Password.GetAdditionalData())
- assert.Empty(t, user.FsConfig.HTTPConfig.Password.GetKey())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.HTTPConfig.APIKey.GetStatus())
- assert.NotEmpty(t, initialAPIKeyPayload)
- assert.Empty(t, user.FsConfig.HTTPConfig.APIKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.HTTPConfig.APIKey.GetKey())
- user.FsConfig.HTTPConfig.Password.SetStatus(sdkkms.SecretStatusSecretBox)
- user.FsConfig.HTTPConfig.Password.SetAdditionalData(util.GenerateUniqueID())
- user.FsConfig.HTTPConfig.Password.SetKey(util.GenerateUniqueID())
- user.FsConfig.HTTPConfig.APIKey.SetStatus(sdkkms.SecretStatusSecretBox)
- user.FsConfig.HTTPConfig.APIKey.SetAdditionalData(util.GenerateUniqueID())
- user.FsConfig.HTTPConfig.APIKey.SetKey(util.GenerateUniqueID())
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.HTTPConfig.Password.GetStatus())
- assert.Equal(t, initialPwdPayload, user.FsConfig.HTTPConfig.Password.GetPayload())
- assert.Empty(t, user.FsConfig.HTTPConfig.Password.GetAdditionalData())
- assert.Empty(t, user.FsConfig.HTTPConfig.Password.GetKey())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.HTTPConfig.APIKey.GetStatus())
- assert.Equal(t, initialAPIKeyPayload, user.FsConfig.HTTPConfig.APIKey.GetPayload())
- assert.Empty(t, user.FsConfig.HTTPConfig.APIKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.HTTPConfig.APIKey.GetKey())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- // also test AddUser
- u := getTestUser()
- u.FsConfig.Provider = sdk.HTTPFilesystemProvider
- u.FsConfig.HTTPConfig = vfs.HTTPFsConfig{
- BaseHTTPFsConfig: sdk.BaseHTTPFsConfig{
- Endpoint: "http://127.0.0.1/httpfs",
- Username: defaultUsername,
- },
- Password: kms.NewPlainSecret(defaultPassword),
- APIKey: kms.NewPlainSecret(defaultTokenAuthUser),
- }
- user, _, err = httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.HTTPConfig.Password.GetStatus())
- assert.NotEmpty(t, user.FsConfig.HTTPConfig.Password.GetPayload())
- assert.Empty(t, user.FsConfig.HTTPConfig.Password.GetAdditionalData())
- assert.Empty(t, user.FsConfig.HTTPConfig.Password.GetKey())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.HTTPConfig.APIKey.GetStatus())
- assert.NotEmpty(t, user.FsConfig.HTTPConfig.APIKey.GetPayload())
- assert.Empty(t, user.FsConfig.HTTPConfig.APIKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.HTTPConfig.APIKey.GetKey())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserAzureBlobConfig(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
- user.FsConfig.AzBlobConfig.Container = "test"
- user.FsConfig.AzBlobConfig.AccountName = "Server-Account-Name"
- user.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret("Server-Account-Key")
- user.FsConfig.AzBlobConfig.Endpoint = "http://127.0.0.1:9000"
- user.FsConfig.AzBlobConfig.UploadPartSize = 8
- user.FsConfig.AzBlobConfig.DownloadPartSize = 6
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- initialPayload := user.FsConfig.AzBlobConfig.AccountKey.GetPayload()
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.AccountKey.GetStatus())
- assert.NotEmpty(t, initialPayload)
- assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetKey())
- user.FsConfig.AzBlobConfig.AccountKey.SetStatus(sdkkms.SecretStatusSecretBox)
- user.FsConfig.AzBlobConfig.AccountKey.SetAdditionalData("data")
- user.FsConfig.AzBlobConfig.AccountKey.SetKey("fake key")
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.AccountKey.GetStatus())
- assert.Equal(t, initialPayload, user.FsConfig.AzBlobConfig.AccountKey.GetPayload())
- assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetKey())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- user.Password = defaultPassword
- user.ID = 0
- user.CreatedAt = 0
- secret := kms.NewSecret(sdkkms.SecretStatusSecretBox, "Server-Account-Key", "", "")
- user.FsConfig.AzBlobConfig.AccountKey = secret
- _, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.Error(t, err)
- user.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret("Server-Account-Key-Test")
- user, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.NoError(t, err)
- initialPayload = user.FsConfig.AzBlobConfig.AccountKey.GetPayload()
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.AccountKey.GetStatus())
- assert.NotEmpty(t, initialPayload)
- assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetKey())
- user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
- user.FsConfig.AzBlobConfig.Container = "test-container"
- user.FsConfig.AzBlobConfig.Endpoint = "http://localhost:9001"
- user.FsConfig.AzBlobConfig.KeyPrefix = "somedir/subdir"
- user.FsConfig.AzBlobConfig.UploadConcurrency = 5
- user.FsConfig.AzBlobConfig.DownloadConcurrency = 4
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.AccountKey.GetStatus())
- assert.NotEmpty(t, initialPayload)
- assert.Equal(t, initialPayload, user.FsConfig.AzBlobConfig.AccountKey.GetPayload())
- assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.AzBlobConfig.AccountKey.GetKey())
- // test user without access key and access secret (SAS)
- user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
- user.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret("https://myaccount.blob.core.windows.net/pictures/profile.jpg?sv=2012-02-12&st=2009-02-09&se=2009-02-10&sr=c&sp=r&si=YWJjZGVmZw%3d%3d&sig=dD80ihBh5jfNpymO5Hg1IdiJIEvHcJpCMiCMnN%2fRnbI%3d")
- user.FsConfig.AzBlobConfig.KeyPrefix = "somedir/subdir"
- user.FsConfig.AzBlobConfig.AccountName = ""
- user.FsConfig.AzBlobConfig.AccountKey = kms.NewEmptySecret()
- user.FsConfig.AzBlobConfig.UploadPartSize = 6
- user.FsConfig.AzBlobConfig.UploadConcurrency = 4
- user.FsConfig.AzBlobConfig.DownloadPartSize = 3
- user.FsConfig.AzBlobConfig.DownloadConcurrency = 5
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Nil(t, user.FsConfig.AzBlobConfig.AccountKey)
- assert.NotNil(t, user.FsConfig.AzBlobConfig.SASURL)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- user.Password = defaultPassword
- user.ID = 0
- user.CreatedAt = 0
- // sas test for add instead of update
- user.FsConfig.AzBlobConfig = vfs.AzBlobFsConfig{
- BaseAzBlobFsConfig: sdk.BaseAzBlobFsConfig{
- Container: user.FsConfig.AzBlobConfig.Container,
- },
- SASURL: kms.NewPlainSecret("http://127.0.0.1/fake/sass/url"),
- }
- user, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.NoError(t, err)
- assert.Nil(t, user.FsConfig.AzBlobConfig.AccountKey)
- initialPayload = user.FsConfig.AzBlobConfig.SASURL.GetPayload()
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.SASURL.GetStatus())
- assert.NotEmpty(t, initialPayload)
- assert.Empty(t, user.FsConfig.AzBlobConfig.SASURL.GetAdditionalData())
- assert.Empty(t, user.FsConfig.AzBlobConfig.SASURL.GetKey())
- user.FsConfig.AzBlobConfig.SASURL.SetStatus(sdkkms.SecretStatusSecretBox)
- user.FsConfig.AzBlobConfig.SASURL.SetAdditionalData("data")
- user.FsConfig.AzBlobConfig.SASURL.SetKey("fake key")
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.AzBlobConfig.SASURL.GetStatus())
- assert.Equal(t, initialPayload, user.FsConfig.AzBlobConfig.SASURL.GetPayload())
- assert.Empty(t, user.FsConfig.AzBlobConfig.SASURL.GetAdditionalData())
- assert.Empty(t, user.FsConfig.AzBlobConfig.SASURL.GetKey())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserCryptFs(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.CryptedFilesystemProvider
- user.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret("crypt passphrase")
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- initialPayload := user.FsConfig.CryptConfig.Passphrase.GetPayload()
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, initialPayload)
- assert.Empty(t, user.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.Empty(t, user.FsConfig.CryptConfig.Passphrase.GetKey())
- user.FsConfig.CryptConfig.Passphrase.SetStatus(sdkkms.SecretStatusSecretBox)
- user.FsConfig.CryptConfig.Passphrase.SetAdditionalData("data")
- user.FsConfig.CryptConfig.Passphrase.SetKey("fake pass key")
- user, bb, err := httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(bb))
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.Equal(t, initialPayload, user.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, user.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.Empty(t, user.FsConfig.CryptConfig.Passphrase.GetKey())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- user.Password = defaultPassword
- user.ID = 0
- user.CreatedAt = 0
- secret := kms.NewSecret(sdkkms.SecretStatusSecretBox, "invalid encrypted payload", "", "")
- user.FsConfig.CryptConfig.Passphrase = secret
- _, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.Error(t, err)
- user.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret("passphrase test")
- user, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.NoError(t, err)
- initialPayload = user.FsConfig.CryptConfig.Passphrase.GetPayload()
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, initialPayload)
- assert.Empty(t, user.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.Empty(t, user.FsConfig.CryptConfig.Passphrase.GetKey())
- user.FsConfig.Provider = sdk.CryptedFilesystemProvider
- user.FsConfig.CryptConfig.Passphrase.SetKey("pass")
- user, bb, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(bb))
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, initialPayload)
- assert.Equal(t, initialPayload, user.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, user.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.Empty(t, user.FsConfig.CryptConfig.Passphrase.GetKey())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserSFTPFs(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.SFTPFilesystemProvider
- user.FsConfig.SFTPConfig.Endpoint = "[::1]:22:22" // invalid endpoint
- user.FsConfig.SFTPConfig.Username = "sftp_user"
- user.FsConfig.SFTPConfig.Password = kms.NewPlainSecret("sftp_pwd")
- user.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret(sftpPrivateKey)
- user.FsConfig.SFTPConfig.Fingerprints = []string{sftpPkeyFingerprint}
- user.FsConfig.SFTPConfig.BufferSize = 2
- user.FsConfig.SFTPConfig.EqualityCheckMode = 1
- _, resp, err := httpdtest.UpdateUser(user, http.StatusBadRequest, "")
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "invalid endpoint")
- user.FsConfig.SFTPConfig.Endpoint = "127.0.0.1"
- _, _, err = httpdtest.UpdateUser(user, http.StatusBadRequest, "")
- assert.Error(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, "127.0.0.1:22", user.FsConfig.SFTPConfig.Endpoint)
- user.FsConfig.SFTPConfig.Endpoint = "127.0.0.1:2022"
- user.FsConfig.SFTPConfig.DisableCouncurrentReads = true
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Equal(t, "/", user.FsConfig.SFTPConfig.Prefix)
- assert.True(t, user.FsConfig.SFTPConfig.DisableCouncurrentReads)
- assert.Equal(t, int64(2), user.FsConfig.SFTPConfig.BufferSize)
- initialPwdPayload := user.FsConfig.SFTPConfig.Password.GetPayload()
- initialPkeyPayload := user.FsConfig.SFTPConfig.PrivateKey.GetPayload()
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.SFTPConfig.Password.GetStatus())
- assert.NotEmpty(t, initialPwdPayload)
- assert.Empty(t, user.FsConfig.SFTPConfig.Password.GetAdditionalData())
- assert.Empty(t, user.FsConfig.SFTPConfig.Password.GetKey())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.SFTPConfig.PrivateKey.GetStatus())
- assert.NotEmpty(t, initialPkeyPayload)
- assert.Empty(t, user.FsConfig.SFTPConfig.PrivateKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.SFTPConfig.PrivateKey.GetKey())
- user.FsConfig.SFTPConfig.Password.SetStatus(sdkkms.SecretStatusSecretBox)
- user.FsConfig.SFTPConfig.Password.SetAdditionalData("adata")
- user.FsConfig.SFTPConfig.Password.SetKey("fake pwd key")
- user.FsConfig.SFTPConfig.PrivateKey.SetStatus(sdkkms.SecretStatusSecretBox)
- user.FsConfig.SFTPConfig.PrivateKey.SetAdditionalData("adata")
- user.FsConfig.SFTPConfig.PrivateKey.SetKey("fake key")
- user.FsConfig.SFTPConfig.DisableCouncurrentReads = false
- user, bb, err := httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(bb))
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.SFTPConfig.Password.GetStatus())
- assert.Equal(t, initialPwdPayload, user.FsConfig.SFTPConfig.Password.GetPayload())
- assert.Empty(t, user.FsConfig.SFTPConfig.Password.GetAdditionalData())
- assert.Empty(t, user.FsConfig.SFTPConfig.Password.GetKey())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.SFTPConfig.PrivateKey.GetStatus())
- assert.Equal(t, initialPkeyPayload, user.FsConfig.SFTPConfig.PrivateKey.GetPayload())
- assert.Empty(t, user.FsConfig.SFTPConfig.PrivateKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.SFTPConfig.PrivateKey.GetKey())
- assert.False(t, user.FsConfig.SFTPConfig.DisableCouncurrentReads)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- user.Password = defaultPassword
- user.ID = 0
- user.CreatedAt = 0
- secret := kms.NewSecret(sdkkms.SecretStatusSecretBox, "invalid encrypted payload", "", "")
- user.FsConfig.SFTPConfig.Password = secret
- _, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.Error(t, err)
- user.FsConfig.SFTPConfig.Password = kms.NewEmptySecret()
- user.FsConfig.SFTPConfig.PrivateKey = secret
- _, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.Error(t, err)
- user.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret(sftpPrivateKey)
- user, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.NoError(t, err)
- initialPkeyPayload = user.FsConfig.SFTPConfig.PrivateKey.GetPayload()
- assert.Nil(t, user.FsConfig.SFTPConfig.Password)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.SFTPConfig.PrivateKey.GetStatus())
- assert.NotEmpty(t, initialPkeyPayload)
- assert.Empty(t, user.FsConfig.SFTPConfig.PrivateKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.SFTPConfig.PrivateKey.GetKey())
- user.FsConfig.Provider = sdk.SFTPFilesystemProvider
- user.FsConfig.SFTPConfig.PrivateKey.SetKey("k")
- user, bb, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(bb))
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.FsConfig.SFTPConfig.PrivateKey.GetStatus())
- assert.NotEmpty(t, initialPkeyPayload)
- assert.Equal(t, initialPkeyPayload, user.FsConfig.SFTPConfig.PrivateKey.GetPayload())
- assert.Empty(t, user.FsConfig.SFTPConfig.PrivateKey.GetAdditionalData())
- assert.Empty(t, user.FsConfig.SFTPConfig.PrivateKey.GetKey())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserHiddenFields(t *testing.T) {
- // sensitive data must be hidden but not deleted from the dataprovider
- usernames := []string{"user1", "user2", "user3", "user4", "user5", "user6"}
- u1 := getTestUser()
- u1.Username = usernames[0]
- u1.FsConfig.Provider = sdk.S3FilesystemProvider
- u1.FsConfig.S3Config.Bucket = "test"
- u1.FsConfig.S3Config.Region = "us-east-1"
- u1.FsConfig.S3Config.AccessKey = "S3-Access-Key"
- u1.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("S3-Access-Secret")
- user1, _, err := httpdtest.AddUser(u1, http.StatusCreated)
- assert.NoError(t, err)
- u2 := getTestUser()
- u2.Username = usernames[1]
- u2.FsConfig.Provider = sdk.GCSFilesystemProvider
- u2.FsConfig.GCSConfig.Bucket = "test"
- u2.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("fake credentials")
- u2.FsConfig.GCSConfig.ACL = "bucketOwnerRead"
- user2, _, err := httpdtest.AddUser(u2, http.StatusCreated)
- assert.NoError(t, err)
- u3 := getTestUser()
- u3.Username = usernames[2]
- u3.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
- u3.FsConfig.AzBlobConfig.Container = "test"
- u3.FsConfig.AzBlobConfig.AccountName = "Server-Account-Name"
- u3.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret("Server-Account-Key")
- user3, _, err := httpdtest.AddUser(u3, http.StatusCreated)
- assert.NoError(t, err)
- u4 := getTestUser()
- u4.Username = usernames[3]
- u4.FsConfig.Provider = sdk.CryptedFilesystemProvider
- u4.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret("test passphrase")
- user4, _, err := httpdtest.AddUser(u4, http.StatusCreated)
- assert.NoError(t, err)
- u5 := getTestUser()
- u5.Username = usernames[4]
- u5.FsConfig.Provider = sdk.SFTPFilesystemProvider
- u5.FsConfig.SFTPConfig.Endpoint = "127.0.0.1:2022"
- u5.FsConfig.SFTPConfig.Username = "sftp_user"
- u5.FsConfig.SFTPConfig.Password = kms.NewPlainSecret("apassword")
- u5.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret(sftpPrivateKey)
- u5.FsConfig.SFTPConfig.Fingerprints = []string{sftpPkeyFingerprint}
- u5.FsConfig.SFTPConfig.Prefix = "/prefix"
- user5, _, err := httpdtest.AddUser(u5, http.StatusCreated)
- assert.NoError(t, err)
- u6 := getTestUser()
- u6.Username = usernames[5]
- u6.FsConfig.Provider = sdk.HTTPFilesystemProvider
- u6.FsConfig.HTTPConfig = vfs.HTTPFsConfig{
- BaseHTTPFsConfig: sdk.BaseHTTPFsConfig{
- Endpoint: "http://127.0.0.1/api/v1",
- Username: defaultUsername,
- },
- Password: kms.NewPlainSecret(defaultPassword),
- APIKey: kms.NewPlainSecret(defaultTokenAuthUser),
- }
- user6, _, err := httpdtest.AddUser(u6, http.StatusCreated)
- assert.NoError(t, err)
- users, _, err := httpdtest.GetUsers(0, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(users), 6)
- for _, username := range usernames {
- user, _, err := httpdtest.GetUserByUsername(username, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, user.Password)
- }
- user1, _, err = httpdtest.GetUserByUsername(user1.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, user1.Password)
- assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.GetKey())
- assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetPayload())
- user2, _, err = httpdtest.GetUserByUsername(user2.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, user2.Password)
- assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetKey())
- assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetAdditionalData())
- assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetStatus())
- assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetPayload())
- user3, _, err = httpdtest.GetUserByUsername(user3.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, user3.Password)
- assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetKey())
- assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
- assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetStatus())
- assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetPayload())
- user4, _, err = httpdtest.GetUserByUsername(user4.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, user4.Password)
- assert.Empty(t, user4.FsConfig.CryptConfig.Passphrase.GetKey())
- assert.Empty(t, user4.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.NotEmpty(t, user4.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, user4.FsConfig.CryptConfig.Passphrase.GetPayload())
- user5, _, err = httpdtest.GetUserByUsername(user5.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, user5.Password)
- assert.Empty(t, user5.FsConfig.SFTPConfig.Password.GetKey())
- assert.Empty(t, user5.FsConfig.SFTPConfig.Password.GetAdditionalData())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.Password.GetStatus())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.Password.GetPayload())
- assert.Empty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetKey())
- assert.Empty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetAdditionalData())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetStatus())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetPayload())
- assert.Equal(t, "/prefix", user5.FsConfig.SFTPConfig.Prefix)
- user6, _, err = httpdtest.GetUserByUsername(user6.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, user6.Password)
- assert.Empty(t, user6.FsConfig.HTTPConfig.Password.GetKey())
- assert.Empty(t, user6.FsConfig.HTTPConfig.Password.GetAdditionalData())
- assert.NotEmpty(t, user6.FsConfig.HTTPConfig.APIKey.GetStatus())
- assert.NotEmpty(t, user6.FsConfig.HTTPConfig.APIKey.GetPayload())
- // finally check that we have all the data inside the data provider
- user1, err = dataprovider.UserExists(user1.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, user1.Password)
- assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetKey())
- assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.NotEmpty(t, user1.FsConfig.S3Config.AccessSecret.GetPayload())
- err = user1.FsConfig.S3Config.AccessSecret.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusPlain, user1.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.Equal(t, u1.FsConfig.S3Config.AccessSecret.GetPayload(), user1.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.GetKey())
- assert.Empty(t, user1.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- user2, err = dataprovider.UserExists(user2.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, user2.Password)
- assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetKey())
- assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetAdditionalData())
- assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetStatus())
- assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetPayload())
- err = user2.FsConfig.GCSConfig.Credentials.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusPlain, user2.FsConfig.GCSConfig.Credentials.GetStatus())
- assert.Equal(t, u2.FsConfig.GCSConfig.Credentials.GetPayload(), user2.FsConfig.GCSConfig.Credentials.GetPayload())
- assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetKey())
- assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetAdditionalData())
- user3, err = dataprovider.UserExists(user3.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, user3.Password)
- assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetKey())
- assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
- assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetStatus())
- assert.NotEmpty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetPayload())
- err = user3.FsConfig.AzBlobConfig.AccountKey.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusPlain, user3.FsConfig.AzBlobConfig.AccountKey.GetStatus())
- assert.Equal(t, u3.FsConfig.AzBlobConfig.AccountKey.GetPayload(), user3.FsConfig.AzBlobConfig.AccountKey.GetPayload())
- assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetKey())
- assert.Empty(t, user3.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
- user4, err = dataprovider.UserExists(user4.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, user4.Password)
- assert.NotEmpty(t, user4.FsConfig.CryptConfig.Passphrase.GetKey())
- assert.NotEmpty(t, user4.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.NotEmpty(t, user4.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, user4.FsConfig.CryptConfig.Passphrase.GetPayload())
- err = user4.FsConfig.CryptConfig.Passphrase.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusPlain, user4.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.Equal(t, u4.FsConfig.CryptConfig.Passphrase.GetPayload(), user4.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, user4.FsConfig.CryptConfig.Passphrase.GetKey())
- assert.Empty(t, user4.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- user5, err = dataprovider.UserExists(user5.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, user5.Password)
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.Password.GetKey())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.Password.GetAdditionalData())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.Password.GetStatus())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.Password.GetPayload())
- err = user5.FsConfig.SFTPConfig.Password.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusPlain, user5.FsConfig.SFTPConfig.Password.GetStatus())
- assert.Equal(t, u5.FsConfig.SFTPConfig.Password.GetPayload(), user5.FsConfig.SFTPConfig.Password.GetPayload())
- assert.Empty(t, user5.FsConfig.SFTPConfig.Password.GetKey())
- assert.Empty(t, user5.FsConfig.SFTPConfig.Password.GetAdditionalData())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetKey())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetAdditionalData())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetStatus())
- assert.NotEmpty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetPayload())
- err = user5.FsConfig.SFTPConfig.PrivateKey.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusPlain, user5.FsConfig.SFTPConfig.PrivateKey.GetStatus())
- assert.Equal(t, u5.FsConfig.SFTPConfig.PrivateKey.GetPayload(), user5.FsConfig.SFTPConfig.PrivateKey.GetPayload())
- assert.Empty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetKey())
- assert.Empty(t, user5.FsConfig.SFTPConfig.PrivateKey.GetAdditionalData())
- user6, err = dataprovider.UserExists(user6.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, user6.Password)
- assert.NotEmpty(t, user6.FsConfig.HTTPConfig.Password.GetKey())
- assert.NotEmpty(t, user6.FsConfig.HTTPConfig.Password.GetAdditionalData())
- assert.NotEmpty(t, user6.FsConfig.HTTPConfig.Password.GetStatus())
- assert.NotEmpty(t, user6.FsConfig.HTTPConfig.Password.GetPayload())
- err = user6.FsConfig.HTTPConfig.Password.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusPlain, user6.FsConfig.HTTPConfig.Password.GetStatus())
- assert.Equal(t, u6.FsConfig.HTTPConfig.Password.GetPayload(), user6.FsConfig.HTTPConfig.Password.GetPayload())
- assert.Empty(t, user6.FsConfig.HTTPConfig.Password.GetKey())
- assert.Empty(t, user6.FsConfig.HTTPConfig.Password.GetAdditionalData())
- // update the GCS user and check that the credentials are preserved
- user2.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
- user2.FsConfig.GCSConfig.ACL = "private"
- _, _, err = httpdtest.UpdateUser(user2, http.StatusOK, "")
- assert.NoError(t, err)
- user2, _, err = httpdtest.GetUserByUsername(user2.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, user2.Password)
- assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetKey())
- assert.Empty(t, user2.FsConfig.GCSConfig.Credentials.GetAdditionalData())
- assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetStatus())
- assert.NotEmpty(t, user2.FsConfig.GCSConfig.Credentials.GetPayload())
- _, err = httpdtest.RemoveUser(user1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user2, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user3, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user4, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user5, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user6, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestSecretObject(t *testing.T) {
- s := kms.NewPlainSecret("test data")
- s.SetAdditionalData("username")
- require.True(t, s.IsValid())
- err := s.Encrypt()
- require.NoError(t, err)
- require.Equal(t, sdkkms.SecretStatusSecretBox, s.GetStatus())
- require.NotEmpty(t, s.GetPayload())
- require.NotEmpty(t, s.GetKey())
- require.True(t, s.IsValid())
- err = s.Decrypt()
- require.NoError(t, err)
- require.Equal(t, sdkkms.SecretStatusPlain, s.GetStatus())
- require.Equal(t, "test data", s.GetPayload())
- require.Empty(t, s.GetKey())
- }
- func TestSecretObjectCompatibility(t *testing.T) {
- // this is manually tested against vault too
- testPayload := "test payload"
- s := kms.NewPlainSecret(testPayload)
- require.True(t, s.IsValid())
- err := s.Encrypt()
- require.NoError(t, err)
- localAsJSON, err := json.Marshal(s)
- assert.NoError(t, err)
- for _, secretStatus := range []string{sdkkms.SecretStatusSecretBox} {
- kmsConfig := config.GetKMSConfig()
- assert.Empty(t, kmsConfig.Secrets.MasterKeyPath)
- if secretStatus == sdkkms.SecretStatusVaultTransit {
- os.Setenv("VAULT_SERVER_URL", "http://127.0.0.1:8200")
- os.Setenv("VAULT_SERVER_TOKEN", "s.9lYGq83MbgG5KR5kfebXVyhJ")
- kmsConfig.Secrets.URL = "hashivault://mykey"
- }
- err := kmsConfig.Initialize()
- assert.NoError(t, err)
- // encrypt without a master key
- secret := kms.NewPlainSecret(testPayload)
- secret.SetAdditionalData("add data")
- err = secret.Encrypt()
- assert.NoError(t, err)
- assert.Equal(t, 0, secret.GetMode())
- secretClone := secret.Clone()
- err = secretClone.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, testPayload, secretClone.GetPayload())
- if secretStatus == sdkkms.SecretStatusVaultTransit {
- // decrypt the local secret now that the provider is vault
- secretLocal := kms.NewEmptySecret()
- err = json.Unmarshal(localAsJSON, secretLocal)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, secretLocal.GetStatus())
- assert.Equal(t, 0, secretLocal.GetMode())
- err = secretLocal.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, testPayload, secretLocal.GetPayload())
- assert.Equal(t, sdkkms.SecretStatusPlain, secretLocal.GetStatus())
- err = secretLocal.Encrypt()
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, secretLocal.GetStatus())
- assert.Equal(t, 0, secretLocal.GetMode())
- }
- asJSON, err := json.Marshal(secret)
- assert.NoError(t, err)
- masterKeyPath := filepath.Join(os.TempDir(), "mkey")
- err = os.WriteFile(masterKeyPath, []byte("test key"), os.ModePerm)
- assert.NoError(t, err)
- config := kms.Configuration{
- Secrets: kms.Secrets{
- MasterKeyPath: masterKeyPath,
- },
- }
- if secretStatus == sdkkms.SecretStatusVaultTransit {
- config.Secrets.URL = "hashivault://mykey"
- }
- err = config.Initialize()
- assert.NoError(t, err)
- // now build the secret from JSON
- secret = kms.NewEmptySecret()
- err = json.Unmarshal(asJSON, secret)
- assert.NoError(t, err)
- assert.Equal(t, 0, secret.GetMode())
- err = secret.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, testPayload, secret.GetPayload())
- err = secret.Encrypt()
- assert.NoError(t, err)
- assert.Equal(t, 1, secret.GetMode())
- err = secret.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, testPayload, secret.GetPayload())
- if secretStatus == sdkkms.SecretStatusVaultTransit {
- // decrypt the local secret encryped without a master key now that
- // the provider is vault and a master key is set.
- // The provider will not change, the master key will be used
- secretLocal := kms.NewEmptySecret()
- err = json.Unmarshal(localAsJSON, secretLocal)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, secretLocal.GetStatus())
- assert.Equal(t, 0, secretLocal.GetMode())
- err = secretLocal.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, testPayload, secretLocal.GetPayload())
- assert.Equal(t, sdkkms.SecretStatusPlain, secretLocal.GetStatus())
- err = secretLocal.Encrypt()
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, secretLocal.GetStatus())
- assert.Equal(t, 1, secretLocal.GetMode())
- }
- err = kmsConfig.Initialize()
- assert.NoError(t, err)
- err = os.Remove(masterKeyPath)
- assert.NoError(t, err)
- if secretStatus == sdkkms.SecretStatusVaultTransit {
- os.Unsetenv("VAULT_SERVER_URL")
- os.Unsetenv("VAULT_SERVER_TOKEN")
- }
- }
- }
- func TestUpdateUserNoCredentials(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- user.Password = ""
- user.PublicKeys = []string{}
- // password and public key will be omitted from json serialization if empty and so they will remain unchanged
- // and no validation error will be raised
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUpdateUserEmptyHomeDir(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- user.HomeDir = ""
- _, _, err = httpdtest.UpdateUser(user, http.StatusBadRequest, "")
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUpdateUserInvalidHomeDir(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- user.HomeDir = "relative_path"
- _, _, err = httpdtest.UpdateUser(user, http.StatusBadRequest, "")
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUpdateNonExistentUser(t *testing.T) {
- _, _, err := httpdtest.UpdateUser(getTestUser(), http.StatusNotFound, "")
- assert.NoError(t, err)
- }
- func TestGetNonExistentUser(t *testing.T) {
- _, _, err := httpdtest.GetUserByUsername("na", http.StatusNotFound)
- assert.NoError(t, err)
- }
- func TestDeleteNonExistentUser(t *testing.T) {
- _, err := httpdtest.RemoveUser(getTestUser(), http.StatusNotFound)
- assert.NoError(t, err)
- }
- func TestAddDuplicateUser(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- _, _, err = httpdtest.AddUser(getTestUser(), http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.Error(t, err, "adding a duplicate user must fail")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestGetUsers(t *testing.T) {
- user1, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- u := getTestUser()
- u.Username = defaultUsername + "1"
- user2, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- users, _, err := httpdtest.GetUsers(0, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(users), 2)
- users, _, err = httpdtest.GetUsers(1, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 1, len(users))
- users, _, err = httpdtest.GetUsers(1, 1, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 1, len(users))
- _, _, err = httpdtest.GetUsers(1, 1, http.StatusInternalServerError)
- assert.Error(t, err)
- _, err = httpdtest.RemoveUser(user1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user2, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestGetQuotaScans(t *testing.T) {
- _, _, err := httpdtest.GetQuotaScans(http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetQuotaScans(http.StatusInternalServerError)
- assert.Error(t, err)
- _, _, err = httpdtest.GetFoldersQuotaScans(http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetFoldersQuotaScans(http.StatusInternalServerError)
- assert.Error(t, err)
- }
- func TestStartQuotaScan(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- _, err = httpdtest.StartQuotaScan(user, http.StatusAccepted)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- folder := vfs.BaseVirtualFolder{
- Name: "vfolder",
- MappedPath: filepath.Join(os.TempDir(), "folder"),
- Description: "virtual folder",
- }
- _, _, err = httpdtest.AddFolder(folder, http.StatusCreated)
- assert.NoError(t, err)
- _, err = httpdtest.StartFolderQuotaScan(folder, http.StatusAccepted)
- assert.NoError(t, err)
- for {
- quotaScan, _, err := httpdtest.GetFoldersQuotaScans(http.StatusOK)
- if !assert.NoError(t, err, "Error getting active scans") {
- break
- }
- if len(quotaScan) == 0 {
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestEmbeddedFolders(t *testing.T) {
- u := getTestUser()
- mappedPath := filepath.Join(os.TempDir(), "mapped_path")
- name := filepath.Base(mappedPath)
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: name,
- UsedQuotaFiles: 1000,
- UsedQuotaSize: 8192,
- LastQuotaUpdate: 123,
- },
- VirtualPath: "/vdir",
- QuotaSize: 4096,
- QuotaFiles: 1,
- })
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.VirtualFolders[0].MappedPath = mappedPath
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- // check that the folder was created
- folder, _, err := httpdtest.GetFolderByName(name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath, folder.MappedPath)
- assert.Equal(t, 0, folder.UsedQuotaFiles)
- assert.Equal(t, int64(0), folder.UsedQuotaSize)
- assert.Equal(t, int64(0), folder.LastQuotaUpdate)
- if assert.Len(t, user.VirtualFolders, 1) {
- assert.Equal(t, mappedPath, user.VirtualFolders[0].MappedPath)
- assert.Equal(t, u.VirtualFolders[0].VirtualPath, user.VirtualFolders[0].VirtualPath)
- assert.Equal(t, u.VirtualFolders[0].QuotaFiles, user.VirtualFolders[0].QuotaFiles)
- assert.Equal(t, u.VirtualFolders[0].QuotaSize, user.VirtualFolders[0].QuotaSize)
- }
- // if the folder already exists we can just reference it by name while adding/updating a user
- u.Username = u.Username + "1"
- u.VirtualFolders[0].MappedPath = ""
- user1, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.EqualError(t, err, "mapped path mismatch")
- if assert.Len(t, user1.VirtualFolders, 1) {
- assert.Equal(t, mappedPath, user1.VirtualFolders[0].MappedPath)
- assert.Equal(t, u.VirtualFolders[0].VirtualPath, user1.VirtualFolders[0].VirtualPath)
- assert.Equal(t, u.VirtualFolders[0].QuotaFiles, user1.VirtualFolders[0].QuotaFiles)
- assert.Equal(t, u.VirtualFolders[0].QuotaSize, user1.VirtualFolders[0].QuotaSize)
- }
- user1.VirtualFolders = u.VirtualFolders
- user1, _, err = httpdtest.UpdateUser(user1, http.StatusOK, "")
- assert.EqualError(t, err, "mapped path mismatch")
- if assert.Len(t, user1.VirtualFolders, 1) {
- assert.Equal(t, mappedPath, user1.VirtualFolders[0].MappedPath)
- assert.Equal(t, u.VirtualFolders[0].VirtualPath, user1.VirtualFolders[0].VirtualPath)
- assert.Equal(t, u.VirtualFolders[0].QuotaFiles, user1.VirtualFolders[0].QuotaFiles)
- assert.Equal(t, u.VirtualFolders[0].QuotaSize, user1.VirtualFolders[0].QuotaSize)
- }
- // now the virtual folder contains all the required paths
- user1, _, err = httpdtest.UpdateUser(user1, http.StatusOK, "")
- assert.NoError(t, err)
- if assert.Len(t, user1.VirtualFolders, 1) {
- assert.Equal(t, mappedPath, user1.VirtualFolders[0].MappedPath)
- assert.Equal(t, u.VirtualFolders[0].VirtualPath, user1.VirtualFolders[0].VirtualPath)
- assert.Equal(t, u.VirtualFolders[0].QuotaFiles, user1.VirtualFolders[0].QuotaFiles)
- assert.Equal(t, u.VirtualFolders[0].QuotaSize, user1.VirtualFolders[0].QuotaSize)
- }
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: name}, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestEmbeddedFoldersUpdate(t *testing.T) {
- u := getTestUser()
- mappedPath := filepath.Join(os.TempDir(), "mapped_path")
- name := filepath.Base(mappedPath)
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: name,
- MappedPath: mappedPath,
- UsedQuotaFiles: 1000,
- UsedQuotaSize: 8192,
- LastQuotaUpdate: 123,
- },
- VirtualPath: "/vdir",
- QuotaSize: 4096,
- QuotaFiles: 1,
- })
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- folder, _, err := httpdtest.GetFolderByName(name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath, folder.MappedPath)
- assert.Equal(t, 0, folder.UsedQuotaFiles)
- assert.Equal(t, int64(0), folder.UsedQuotaSize)
- assert.Equal(t, int64(0), folder.LastQuotaUpdate)
- assert.Empty(t, folder.Description)
- assert.Equal(t, sdk.LocalFilesystemProvider, folder.FsConfig.Provider)
- assert.Len(t, folder.Users, 1)
- assert.Contains(t, folder.Users, user.Username)
- // update a field on the folder
- description := "updatedDesc"
- folder.MappedPath = mappedPath + "_update"
- folder.Description = description
- folder, _, err = httpdtest.UpdateFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath+"_update", folder.MappedPath)
- assert.Equal(t, 0, folder.UsedQuotaFiles)
- assert.Equal(t, int64(0), folder.UsedQuotaSize)
- assert.Equal(t, int64(0), folder.LastQuotaUpdate)
- assert.Equal(t, description, folder.Description)
- assert.Equal(t, sdk.LocalFilesystemProvider, folder.FsConfig.Provider)
- // check that the user gets the changes
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- userFolder := user.VirtualFolders[0].BaseVirtualFolder
- assert.Equal(t, mappedPath+"_update", folder.MappedPath)
- assert.Equal(t, 0, userFolder.UsedQuotaFiles)
- assert.Equal(t, int64(0), userFolder.UsedQuotaSize)
- assert.Equal(t, int64(0), userFolder.LastQuotaUpdate)
- assert.Equal(t, description, userFolder.Description)
- assert.Equal(t, sdk.LocalFilesystemProvider, userFolder.FsConfig.Provider)
- // now update the folder embedding it inside the user
- user.VirtualFolders = []vfs.VirtualFolder{
- {
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: name,
- MappedPath: "",
- UsedQuotaFiles: 1000,
- UsedQuotaSize: 8192,
- LastQuotaUpdate: 123,
- FsConfig: vfs.Filesystem{
- Provider: sdk.S3FilesystemProvider,
- S3Config: vfs.S3FsConfig{
- BaseS3FsConfig: sdk.BaseS3FsConfig{
- Bucket: "test",
- Region: "us-east-1",
- AccessKey: "akey",
- Endpoint: "http://127.0.1.1:9090",
- },
- AccessSecret: kms.NewPlainSecret("asecret"),
- },
- },
- },
- VirtualPath: "/vdir1",
- QuotaSize: 4096,
- QuotaFiles: 1,
- },
- }
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- userFolder = user.VirtualFolders[0].BaseVirtualFolder
- assert.Equal(t, 0, userFolder.UsedQuotaFiles)
- assert.Equal(t, int64(0), userFolder.UsedQuotaSize)
- assert.Equal(t, int64(0), userFolder.LastQuotaUpdate)
- assert.Empty(t, userFolder.Description)
- assert.Equal(t, sdk.S3FilesystemProvider, userFolder.FsConfig.Provider)
- assert.Equal(t, "test", userFolder.FsConfig.S3Config.Bucket)
- assert.Equal(t, "us-east-1", userFolder.FsConfig.S3Config.Region)
- assert.Equal(t, "http://127.0.1.1:9090", userFolder.FsConfig.S3Config.Endpoint)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, userFolder.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.NotEmpty(t, userFolder.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Empty(t, userFolder.FsConfig.S3Config.AccessSecret.GetKey())
- assert.Empty(t, userFolder.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- // confirm the changes
- folder, _, err = httpdtest.GetFolderByName(name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 0, folder.UsedQuotaFiles)
- assert.Equal(t, int64(0), folder.UsedQuotaSize)
- assert.Equal(t, int64(0), folder.LastQuotaUpdate)
- assert.Empty(t, folder.Description)
- assert.Equal(t, sdk.S3FilesystemProvider, folder.FsConfig.Provider)
- assert.Equal(t, "test", folder.FsConfig.S3Config.Bucket)
- assert.Equal(t, "us-east-1", folder.FsConfig.S3Config.Region)
- assert.Equal(t, "http://127.0.1.1:9090", folder.FsConfig.S3Config.Endpoint)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, folder.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.NotEmpty(t, folder.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Empty(t, folder.FsConfig.S3Config.AccessSecret.GetKey())
- assert.Empty(t, folder.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- // now update folder usage limits and check that a folder update will not change them
- folder.UsedQuotaFiles = 100
- folder.UsedQuotaSize = 32768
- _, err = httpdtest.UpdateFolderQuotaUsage(folder, "reset", http.StatusOK)
- assert.NoError(t, err)
- folder, _, err = httpdtest.GetFolderByName(name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 100, folder.UsedQuotaFiles)
- assert.Equal(t, int64(32768), folder.UsedQuotaSize)
- assert.Greater(t, folder.LastQuotaUpdate, int64(0))
- assert.Equal(t, sdk.S3FilesystemProvider, folder.FsConfig.Provider)
- assert.Equal(t, "test", folder.FsConfig.S3Config.Bucket)
- assert.Equal(t, "us-east-1", folder.FsConfig.S3Config.Region)
- assert.Equal(t, "http://127.0.1.1:9090", folder.FsConfig.S3Config.Endpoint)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, folder.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.NotEmpty(t, folder.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Empty(t, folder.FsConfig.S3Config.AccessSecret.GetKey())
- assert.Empty(t, folder.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- user.VirtualFolders[0].FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("updated secret")
- user, resp, err := httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(resp))
- userFolder = user.VirtualFolders[0].BaseVirtualFolder
- assert.Equal(t, 100, userFolder.UsedQuotaFiles)
- assert.Equal(t, int64(32768), userFolder.UsedQuotaSize)
- assert.Greater(t, userFolder.LastQuotaUpdate, int64(0))
- assert.Empty(t, userFolder.Description)
- assert.Equal(t, sdk.S3FilesystemProvider, userFolder.FsConfig.Provider)
- assert.Equal(t, "test", userFolder.FsConfig.S3Config.Bucket)
- assert.Equal(t, "us-east-1", userFolder.FsConfig.S3Config.Region)
- assert.Equal(t, "http://127.0.1.1:9090", userFolder.FsConfig.S3Config.Endpoint)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, userFolder.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.NotEmpty(t, userFolder.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Empty(t, userFolder.FsConfig.S3Config.AccessSecret.GetKey())
- assert.Empty(t, userFolder.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: name}, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUpdateFolderQuotaUsage(t *testing.T) {
- f := vfs.BaseVirtualFolder{
- Name: "vdir",
- MappedPath: filepath.Join(os.TempDir(), "folder"),
- }
- usedQuotaFiles := 1
- usedQuotaSize := int64(65535)
- f.UsedQuotaFiles = usedQuotaFiles
- f.UsedQuotaSize = usedQuotaSize
- folder, _, err := httpdtest.AddFolder(f, http.StatusCreated)
- if assert.NoError(t, err) {
- assert.Equal(t, usedQuotaFiles, folder.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize, folder.UsedQuotaSize)
- }
- _, err = httpdtest.UpdateFolderQuotaUsage(folder, "invalid mode", http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.UpdateFolderQuotaUsage(f, "reset", http.StatusOK)
- assert.NoError(t, err)
- folder, _, err = httpdtest.GetFolderByName(f.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, usedQuotaFiles, folder.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize, folder.UsedQuotaSize)
- _, err = httpdtest.UpdateFolderQuotaUsage(f, "add", http.StatusOK)
- assert.NoError(t, err)
- folder, _, err = httpdtest.GetFolderByName(f.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 2*usedQuotaFiles, folder.UsedQuotaFiles)
- assert.Equal(t, 2*usedQuotaSize, folder.UsedQuotaSize)
- f.UsedQuotaSize = -1
- _, err = httpdtest.UpdateFolderQuotaUsage(f, "", http.StatusBadRequest)
- assert.NoError(t, err)
- f.UsedQuotaSize = usedQuotaSize
- f.Name = f.Name + "1"
- _, err = httpdtest.UpdateFolderQuotaUsage(f, "", http.StatusNotFound)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestGetVersion(t *testing.T) {
- _, _, err := httpdtest.GetVersion(http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetVersion(http.StatusInternalServerError)
- assert.Error(t, err, "get version request must succeed, we requested to check a wrong status code")
- }
- func TestGetStatus(t *testing.T) {
- _, _, err := httpdtest.GetStatus(http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetStatus(http.StatusBadRequest)
- assert.Error(t, err, "get provider status request must succeed, we requested to check a wrong status code")
- }
- func TestGetConnections(t *testing.T) {
- _, _, err := httpdtest.GetConnections(http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetConnections(http.StatusInternalServerError)
- assert.Error(t, err, "get sftp connections request must succeed, we requested to check a wrong status code")
- }
- func TestCloseActiveConnection(t *testing.T) {
- _, err := httpdtest.CloseConnection("non_existent_id", http.StatusNotFound)
- assert.NoError(t, err)
- user := getTestUser()
- c := common.NewBaseConnection("connID", common.ProtocolSFTP, "", "", user)
- fakeConn := &fakeConnection{
- BaseConnection: c,
- }
- err = common.Connections.Add(fakeConn)
- assert.NoError(t, err)
- _, err = httpdtest.CloseConnection(c.GetID(), http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, common.Connections.GetStats(""), 0)
- }
- func TestCloseConnectionAfterUserUpdateDelete(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- c := common.NewBaseConnection("connID", common.ProtocolFTP, "", "", user)
- fakeConn := &fakeConnection{
- BaseConnection: c,
- }
- err = common.Connections.Add(fakeConn)
- assert.NoError(t, err)
- c1 := common.NewBaseConnection("connID1", common.ProtocolSFTP, "", "", user)
- fakeConn1 := &fakeConnection{
- BaseConnection: c1,
- }
- err = common.Connections.Add(fakeConn1)
- assert.NoError(t, err)
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "0")
- assert.NoError(t, err)
- assert.Len(t, common.Connections.GetStats(""), 2)
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "1")
- assert.NoError(t, err)
- assert.Len(t, common.Connections.GetStats(""), 0)
- err = common.Connections.Add(fakeConn)
- assert.NoError(t, err)
- err = common.Connections.Add(fakeConn1)
- assert.NoError(t, err)
- assert.Len(t, common.Connections.GetStats(""), 2)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, common.Connections.GetStats(""), 0)
- }
- func TestAdminGenerateRecoveryCodesSaveError(t *testing.T) {
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.NamingRules = 7
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- a := getTestAdmin()
- a.Username = "adMiN@example.com "
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], admin.Username)
- assert.NoError(t, err)
- admin.Filters.TOTPConfig = dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- }
- admin.Password = defaultTokenAuthPass
- err = dataprovider.UpdateAdmin(&admin, "", "", "")
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(a.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- passcode, err := generateTOTPPasscode(secret)
- assert.NoError(t, err)
- adminAPIToken, err := getJWTAPITokenFromTestServerWithPasscode(a.Username, defaultTokenAuthPass, passcode)
- assert.NoError(t, err)
- assert.NotEmpty(t, adminAPIToken)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- if config.GetProviderConf().Driver == dataprovider.MemoryDataProviderName {
- return
- }
- req, err := http.NewRequest(http.MethodPost, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, adminAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestNamingRules(t *testing.T) {
- smtpCfg := smtp.Config{
- Host: "127.0.0.1",
- Port: 3525,
- TemplatesPath: "templates",
- }
- err := smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.NamingRules = 7
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- u := getTestUser()
- u.Username = " uSeR@user.me "
- u.Email = dataprovider.ConvertName(u.Username)
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, "user@user.me", user.Username)
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
- assert.NoError(t, err)
- user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- Protocols: []string{common.ProtocolSSH},
- }
- user.Password = u.Password
- err = dataprovider.UpdateUser(&user, "", "", "")
- assert.NoError(t, err)
- user.Username = u.Username
- user.AdditionalInfo = "info"
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(u.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, user.Filters.TOTPConfig.Enabled)
- r := getTestRole()
- r.Name = "role@mycompany"
- role, _, err := httpdtest.AddRole(r, http.StatusCreated)
- assert.NoError(t, err)
- a := getTestAdmin()
- a.Username = "admiN@example.com "
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, "admin@example.com", admin.Username)
- admin.Email = dataprovider.ConvertName(a.Username)
- admin.Username = a.Username
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(a.Username, http.StatusOK)
- assert.NoError(t, err)
- f := vfs.BaseVirtualFolder{
- Name: "文件夹AB",
- MappedPath: filepath.Clean(os.TempDir()),
- }
- folder, resp, err := httpdtest.AddFolder(f, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Equal(t, "文件夹ab", folder.Name)
- folder.Name = f.Name
- folder.Description = folder.Name
- folder, resp, err = httpdtest.UpdateFolder(folder, http.StatusOK)
- assert.NoError(t, err, string(resp))
- folder, resp, err = httpdtest.GetFolderByName(f.Name, http.StatusOK)
- assert.NoError(t, err, string(resp))
- _, err = httpdtest.RemoveFolder(f, http.StatusOK)
- assert.NoError(t, err)
- token, err := getJWTWebClientTokenFromTestServer(u.Username, defaultPassword)
- assert.NoError(t, err)
- assert.NotEmpty(t, token)
- adminAPIToken, err := getJWTAPITokenFromTestServer(a.Username, defaultTokenAuthPass)
- assert.NoError(t, err)
- assert.NotEmpty(t, adminAPIToken)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- if config.GetProviderConf().Driver == dataprovider.MemoryDataProviderName {
- return
- }
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- token, err = getJWTWebClientTokenFromTestServer(user.Username, defaultPassword)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- req, err := http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- // test user reset password. Setting the new password will fail because the username is not valid
- form = make(url.Values)
- form.Set("username", user.Username)
- form.Set(csrfFormToken, csrfToken)
- lastResetCode = ""
- req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("code", lastResetCode)
- form.Set("password", defaultPassword)
- req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- adminAPIToken, err = getJWTAPITokenFromTestServer(admin.Username, defaultTokenAuthPass)
- assert.NoError(t, err)
- userAPIToken, err := getJWTAPIUserTokenFromTestServer(user.Username, defaultPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userPath+"/"+user.Username+"/2fa/disable", nil)
- assert.NoError(t, err)
- setBearerForReq(req, adminAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- req, err = http.NewRequest(http.MethodPost, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, userAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- apiKeyAuthReq := make(map[string]bool)
- apiKeyAuthReq["allow_api_key_auth"] = true
- asJSON, err := json.Marshal(apiKeyAuthReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, userAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- token, err = getJWTWebTokenFromTestServer(admin.Username, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err = getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminRolePath, role.Name), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- apiKeyAuthReq = make(map[string]bool)
- apiKeyAuthReq["allow_api_key_auth"] = true
- asJSON, err = json.Marshal(apiKeyAuthReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, adminAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- req, err = http.NewRequest(http.MethodPut, adminPath+"/"+admin.Username+"/2fa/disable", nil)
- assert.NoError(t, err)
- setBearerForReq(req, adminAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the following characters are allowed")
- // test admin reset password
- form = make(url.Values)
- form.Set("username", admin.Username)
- form.Set(csrfFormToken, csrfToken)
- lastResetCode = ""
- req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("code", lastResetCode)
- form.Set("password", defaultPassword)
- req, err = http.NewRequest(http.MethodPost, webAdminResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "unable to set the new password")
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- smtpCfg = smtp.Config{}
- err = smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- }
- func TestSaveErrors(t *testing.T) {
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.NamingRules = 1
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- recCode := "recovery code"
- recoveryCodes := []dataprovider.RecoveryCode{
- {
- Secret: kms.NewPlainSecret(recCode),
- Used: false,
- },
- }
- u := getTestUser()
- u.Username = "user@example.com"
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
- assert.NoError(t, err)
- user.Password = u.Password
- user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- Protocols: []string{common.ProtocolSSH, common.ProtocolHTTP},
- }
- user.Filters.RecoveryCodes = recoveryCodes
- err = dataprovider.UpdateUser(&user, "", "", "")
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, user.Filters.TOTPConfig.Enabled)
- assert.Len(t, user.Filters.RecoveryCodes, 1)
- a := getTestAdmin()
- a.Username = "admin@example.com"
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- admin.Email = admin.Username
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- admin.Password = a.Password
- admin.Filters.TOTPConfig = dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- }
- admin.Filters.RecoveryCodes = recoveryCodes
- err = dataprovider.UpdateAdmin(&admin, "", "", "")
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- assert.Len(t, admin.Filters.RecoveryCodes, 1)
- r := getTestRole()
- r.Name = "role@mycompany"
- role, _, err := httpdtest.AddRole(r, http.StatusCreated)
- assert.NoError(t, err)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- if config.GetProviderConf().Driver == dataprovider.MemoryDataProviderName {
- return
- }
- _, resp, err := httpdtest.UpdateRole(role, http.StatusBadRequest)
- assert.NoError(t, err)
- assert.Contains(t, string(resp), "the following characters are allowed")
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := getLoginForm(a.Username, a.Password, csrfToken)
- req, err := http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr := executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminTwoFactorPath, rr.Header().Get("Location"))
- cookie, err := getCookieFromResponse(rr)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set("recovery_code", recCode)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code)
- assert.Contains(t, rr.Body.String(), "unable to set the recovery code as used")
- csrfToken, err = getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- form = getLoginForm(u.Username, u.Password, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientTwoFactorPath, rr.Header().Get("Location"))
- cookie, err = getCookieFromResponse(rr)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set("recovery_code", recCode)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code)
- assert.Contains(t, rr.Body.String(), "unable to set the recovery code as used")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserBaseDir(t *testing.T) {
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.UsersBaseDir = homeBasePath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- u := getTestUser()
- u.HomeDir = ""
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- if assert.Error(t, err) {
- assert.EqualError(t, err, "home dir mismatch")
- }
- assert.Equal(t, filepath.Join(providerConf.UsersBaseDir, u.Username), user.HomeDir)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- }
- func TestQuotaTrackingDisabled(t *testing.T) {
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.TrackQuota = 0
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- // user quota scan must fail
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- _, err = httpdtest.StartQuotaScan(user, http.StatusForbidden)
- assert.NoError(t, err)
- _, err = httpdtest.UpdateQuotaUsage(user, "", http.StatusForbidden)
- assert.NoError(t, err)
- _, err = httpdtest.UpdateTransferQuotaUsage(user, "", http.StatusForbidden)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- // folder quota scan must fail
- folder := vfs.BaseVirtualFolder{
- Name: "folder_quota_test",
- MappedPath: filepath.Clean(os.TempDir()),
- }
- folder, resp, err := httpdtest.AddFolder(folder, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- _, err = httpdtest.StartFolderQuotaScan(folder, http.StatusForbidden)
- assert.NoError(t, err)
- _, err = httpdtest.UpdateFolderQuotaUsage(folder, "", http.StatusForbidden)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- }
- func TestProviderErrors(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- userAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- userWebToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- token, _, err := httpdtest.GetToken(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- testServerToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- httpdtest.SetJWTToken(token)
- err = dataprovider.Close()
- assert.NoError(t, err)
- _, _, err = httpdtest.GetUserByUsername("na", http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetUsers(1, 0, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetGroups(1, 0, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetAdmins(1, 0, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetAPIKeys(1, 0, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetEventActions(1, 0, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetEventRules(1, 0, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetRoles(1, 0, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.UpdateRole(getTestRole(), http.StatusInternalServerError)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, userSharesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, userAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, userAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- // password reset errors
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("username", "username")
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Error retrieving your account, please try again later")
- req, err = http.NewRequest(http.MethodGet, webClientSharesPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, userWebToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webClientSharePath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, userWebToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webClientSharePath+"/shareID", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, userWebToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodPost, webClientSharePath+"/shareID", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, userWebToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- _, _, err = httpdtest.UpdateUser(dataprovider.User{BaseUser: sdk.BaseUser{Username: "auser"}}, http.StatusInternalServerError, "")
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(dataprovider.User{BaseUser: sdk.BaseUser{Username: "auser"}}, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: "aname"}, http.StatusInternalServerError)
- assert.NoError(t, err)
- status, _, err := httpdtest.GetStatus(http.StatusOK)
- if assert.NoError(t, err) {
- assert.False(t, status.DataProvider.IsActive)
- }
- _, _, err = httpdtest.Dumpdata("backup.json", "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetFolders(0, 0, http.StatusInternalServerError)
- assert.NoError(t, err)
- user = getTestUser()
- user.ID = 1
- backupData := dataprovider.BackupData{}
- backupData.Users = append(backupData.Users, user)
- backupContent, err := json.Marshal(backupData)
- assert.NoError(t, err)
- backupFilePath := filepath.Join(backupsPath, "backup.json")
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- backupData.Folders = append(backupData.Folders, vfs.BaseVirtualFolder{Name: "testFolder", MappedPath: filepath.Clean(os.TempDir())})
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- backupData.Users = nil
- backupData.Folders = nil
- backupData.Groups = append(backupData.Groups, getTestGroup())
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- backupData.Groups = nil
- backupData.Admins = append(backupData.Admins, getTestAdmin())
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- backupData.Users = nil
- backupData.Folders = nil
- backupData.Admins = nil
- backupData.APIKeys = append(backupData.APIKeys, dataprovider.APIKey{
- Name: "name",
- KeyID: util.GenerateUniqueID(),
- Key: fmt.Sprintf("%v.%v", util.GenerateUniqueID(), util.GenerateUniqueID()),
- Scope: dataprovider.APIKeyScopeUser,
- })
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- backupData.APIKeys = nil
- backupData.Shares = append(backupData.Shares, dataprovider.Share{
- Name: util.GenerateUniqueID(),
- ShareID: util.GenerateUniqueID(),
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- Username: defaultUsername,
- })
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- backupData = dataprovider.BackupData{
- EventActions: []dataprovider.BaseEventAction{
- {
- Name: "quota reset",
- Type: dataprovider.ActionTypeFolderQuotaReset,
- },
- },
- }
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- backupData = dataprovider.BackupData{
- EventRules: []dataprovider.EventRule{
- {
- Name: "quota reset",
- Trigger: dataprovider.EventTriggerSchedule,
- Conditions: dataprovider.EventConditions{
- Schedules: []dataprovider.Schedule{
- {
- Hours: "2",
- DayOfWeek: "1",
- DayOfMonth: "2",
- Month: "3",
- },
- },
- },
- Actions: []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: "unknown action",
- },
- Order: 1,
- },
- },
- },
- },
- }
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- backupData = dataprovider.BackupData{
- Roles: []dataprovider.Role{
- {
- Name: "role1",
- },
- },
- }
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
- assert.NoError(t, err)
- err = os.Remove(backupFilePath)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webUserPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webTemplateUser+"?from=auser", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webGroupPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webTemplateUser, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webGroupsPath+"?qlimit=a", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webGroupPath, "groupname"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodPost, path.Join(webGroupPath, "grpname"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webTemplateFolder+"?from=afolder", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webAdminEventActionPath, "actionname"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, "actionname"), bytes.NewBuffer(nil))
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminEventActionsPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webAdminEventRulePath, "rulename"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventRulePath, "rulename"), bytes.NewBuffer(nil))
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminEventRulesPath+"?qlimit=10", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminEventRulePath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, testServerToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- httpdtest.SetJWTToken("")
- }
- func TestFolders(t *testing.T) {
- folder := vfs.BaseVirtualFolder{
- Name: "name",
- MappedPath: "relative path",
- Users: []string{"1", "2", "3"},
- FsConfig: vfs.Filesystem{
- Provider: sdk.CryptedFilesystemProvider,
- CryptConfig: vfs.CryptFsConfig{
- Passphrase: kms.NewPlainSecret("asecret"),
- },
- },
- }
- _, _, err := httpdtest.AddFolder(folder, http.StatusBadRequest)
- assert.NoError(t, err)
- folder.MappedPath = filepath.Clean(os.TempDir())
- folder1, resp, err := httpdtest.AddFolder(folder, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Equal(t, folder.Name, folder1.Name)
- assert.Equal(t, folder.MappedPath, folder1.MappedPath)
- assert.Equal(t, 0, folder1.UsedQuotaFiles)
- assert.Equal(t, int64(0), folder1.UsedQuotaSize)
- assert.Equal(t, int64(0), folder1.LastQuotaUpdate)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, folder1.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, folder1.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, folder1.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.Empty(t, folder1.FsConfig.CryptConfig.Passphrase.GetKey())
- assert.Len(t, folder1.Users, 0)
- // adding a duplicate folder must fail
- _, _, err = httpdtest.AddFolder(folder, http.StatusCreated)
- assert.Error(t, err)
- folder.MappedPath = filepath.Join(os.TempDir(), "vfolder")
- folder.Name = filepath.Base(folder.MappedPath)
- folder.UsedQuotaFiles = 1
- folder.UsedQuotaSize = 345
- folder.LastQuotaUpdate = 10
- folder2, _, err := httpdtest.AddFolder(folder, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Equal(t, 1, folder2.UsedQuotaFiles)
- assert.Equal(t, int64(345), folder2.UsedQuotaSize)
- assert.Equal(t, int64(10), folder2.LastQuotaUpdate)
- assert.Len(t, folder2.Users, 0)
- folders, _, err := httpdtest.GetFolders(0, 0, http.StatusOK)
- assert.NoError(t, err)
- numResults := len(folders)
- assert.GreaterOrEqual(t, numResults, 2)
- found := false
- for _, f := range folders {
- if f.Name == folder1.Name {
- found = true
- assert.Equal(t, folder1.MappedPath, f.MappedPath)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, f.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, f.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, f.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.Empty(t, f.FsConfig.CryptConfig.Passphrase.GetKey())
- assert.Len(t, f.Users, 0)
- }
- }
- assert.True(t, found)
- folders, _, err = httpdtest.GetFolders(0, 1, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folders, numResults-1)
- folders, _, err = httpdtest.GetFolders(1, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, folders, 1)
- f, _, err := httpdtest.GetFolderByName(folder1.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, folder1.Name, f.Name)
- assert.Equal(t, folder1.MappedPath, f.MappedPath)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, f.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, f.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, f.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- assert.Empty(t, f.FsConfig.CryptConfig.Passphrase.GetKey())
- assert.Len(t, f.Users, 0)
- f, _, err = httpdtest.GetFolderByName(folder2.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, folder2.Name, f.Name)
- assert.Equal(t, folder2.MappedPath, f.MappedPath)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{
- Name: "invalid",
- }, http.StatusNotFound)
- assert.NoError(t, err)
- _, _, err = httpdtest.UpdateFolder(vfs.BaseVirtualFolder{Name: "notfound"}, http.StatusNotFound)
- assert.NoError(t, err)
- folder1.MappedPath = "a/relative/path"
- _, _, err = httpdtest.UpdateFolder(folder1, http.StatusBadRequest)
- assert.NoError(t, err)
- folder1.MappedPath = filepath.Join(os.TempDir(), "updated")
- folder1.Description = "updated folder description"
- f, resp, err = httpdtest.UpdateFolder(folder1, http.StatusOK)
- assert.NoError(t, err, string(resp))
- assert.Equal(t, folder1.MappedPath, f.MappedPath)
- assert.Equal(t, folder1.Description, f.Description)
- _, err = httpdtest.RemoveFolder(folder1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(folder2, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestDumpdata(t *testing.T) {
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- _, rawResp, err := httpdtest.Dumpdata("", "", "", http.StatusBadRequest)
- assert.NoError(t, err, string(rawResp))
- _, _, err = httpdtest.Dumpdata(filepath.Join(backupsPath, "backup.json"), "", "", http.StatusBadRequest)
- assert.NoError(t, err)
- _, rawResp, err = httpdtest.Dumpdata("../backup.json", "", "", http.StatusBadRequest)
- assert.NoError(t, err, string(rawResp))
- _, rawResp, err = httpdtest.Dumpdata("backup.json", "", "0", http.StatusOK)
- assert.NoError(t, err, string(rawResp))
- response, _, err := httpdtest.Dumpdata("", "1", "0", http.StatusOK)
- assert.NoError(t, err)
- _, ok := response["admins"]
- assert.True(t, ok)
- _, ok = response["users"]
- assert.True(t, ok)
- _, ok = response["groups"]
- assert.True(t, ok)
- _, ok = response["folders"]
- assert.True(t, ok)
- _, ok = response["api_keys"]
- assert.True(t, ok)
- _, ok = response["shares"]
- assert.True(t, ok)
- _, ok = response["version"]
- assert.True(t, ok)
- _, rawResp, err = httpdtest.Dumpdata("backup.json", "", "1", http.StatusOK)
- assert.NoError(t, err, string(rawResp))
- err = os.Remove(filepath.Join(backupsPath, "backup.json"))
- assert.NoError(t, err)
- if runtime.GOOS != osWindows {
- err = os.Chmod(backupsPath, 0001)
- assert.NoError(t, err)
- _, _, err = httpdtest.Dumpdata("bck.json", "", "", http.StatusForbidden)
- assert.NoError(t, err)
- // subdir cannot be created
- _, _, err = httpdtest.Dumpdata(filepath.Join("subdir", "bck.json"), "", "", http.StatusForbidden)
- assert.NoError(t, err)
- err = os.Chmod(backupsPath, 0755)
- assert.NoError(t, err)
- }
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- }
- func TestDefenderAPI(t *testing.T) {
- oldConfig := config.GetCommonConfig()
- drivers := []string{common.DefenderDriverMemory}
- if isDbDefenderSupported() {
- drivers = append(drivers, common.DefenderDriverProvider)
- }
- for _, driver := range drivers {
- cfg := config.GetCommonConfig()
- cfg.DefenderConfig.Enabled = true
- cfg.DefenderConfig.Driver = driver
- cfg.DefenderConfig.Threshold = 3
- cfg.DefenderConfig.ScoreLimitExceeded = 2
- err := common.Initialize(cfg, 0)
- assert.NoError(t, err)
- ip := "::1"
- hosts, _, err := httpdtest.GetDefenderHosts(http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, hosts, 0)
- _, err = httpdtest.RemoveDefenderHostByIP(ip, http.StatusNotFound)
- assert.NoError(t, err)
- common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
- hosts, _, err = httpdtest.GetDefenderHosts(http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, hosts, 1) {
- host := hosts[0]
- assert.Empty(t, host.GetBanTime())
- assert.Equal(t, 2, host.Score)
- assert.Equal(t, ip, host.IP)
- }
- host, _, err := httpdtest.GetDefenderHostByIP(ip, http.StatusOK)
- assert.NoError(t, err)
- assert.Empty(t, host.GetBanTime())
- assert.Equal(t, 2, host.Score)
- common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
- hosts, _, err = httpdtest.GetDefenderHosts(http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, hosts, 1) {
- host := hosts[0]
- assert.NotEmpty(t, host.GetBanTime())
- assert.Equal(t, 0, host.Score)
- assert.Equal(t, ip, host.IP)
- }
- host, _, err = httpdtest.GetDefenderHostByIP(ip, http.StatusOK)
- assert.NoError(t, err)
- assert.NotEmpty(t, host.GetBanTime())
- assert.Equal(t, 0, host.Score)
- _, err = httpdtest.RemoveDefenderHostByIP(ip, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetDefenderHostByIP(ip, http.StatusNotFound)
- assert.NoError(t, err)
- common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
- common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
- hosts, _, err = httpdtest.GetDefenderHosts(http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, hosts, 1)
- _, err = httpdtest.RemoveDefenderHostByIP(ip, http.StatusOK)
- assert.NoError(t, err)
- host, _, err = httpdtest.GetDefenderHostByIP(ip, http.StatusNotFound)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveDefenderHostByIP(ip, http.StatusNotFound)
- assert.NoError(t, err)
- host, _, err = httpdtest.GetDefenderHostByIP("invalid_ip", http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveDefenderHostByIP("invalid_ip", http.StatusBadRequest)
- assert.NoError(t, err)
- if driver == common.DefenderDriverProvider {
- err = dataprovider.CleanupDefender(util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Hour)))
- assert.NoError(t, err)
- }
- }
- err := common.Initialize(oldConfig, 0)
- require.NoError(t, err)
- }
- func TestDefenderAPIErrors(t *testing.T) {
- if isDbDefenderSupported() {
- oldConfig := config.GetCommonConfig()
- cfg := config.GetCommonConfig()
- cfg.DefenderConfig.Enabled = true
- cfg.DefenderConfig.Driver = common.DefenderDriverProvider
- err := common.Initialize(cfg, 0)
- require.NoError(t, err)
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- err = dataprovider.Close()
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, defenderHosts, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- err = common.Initialize(oldConfig, 0)
- require.NoError(t, err)
- }
- }
- func TestRestoreShares(t *testing.T) {
- // shares should be restored preserving the UsedTokens, CreatedAt, LastUseAt, UpdatedAt,
- // and ExpiresAt, so an expired share can be restored while we cannot create an already
- // expired share
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- share := dataprovider.Share{
- ShareID: shortuuid.New(),
- Name: "share name",
- Description: "share description",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- Username: user.Username,
- CreatedAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-144 * time.Hour)),
- UpdatedAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-96 * time.Hour)),
- LastUseAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-64 * time.Hour)),
- ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(-48 * time.Hour)),
- MaxTokens: 10,
- UsedTokens: 8,
- AllowFrom: []string{"127.0.0.0/8"},
- }
- backupData := dataprovider.BackupData{}
- backupData.Shares = append(backupData.Shares, share)
- backupContent, err := json.Marshal(backupData)
- assert.NoError(t, err)
- _, _, err = httpdtest.LoaddataFromPostBody(backupContent, "0", "0", http.StatusOK)
- assert.NoError(t, err)
- shareGet, err := dataprovider.ShareExists(share.ShareID, user.Username)
- assert.NoError(t, err)
- assert.Equal(t, share, shareGet)
- share.CreatedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-142 * time.Hour))
- share.UpdatedAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-92 * time.Hour))
- share.LastUseAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-62 * time.Hour))
- share.UsedTokens = 6
- backupData.Shares = []dataprovider.Share{share}
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- _, _, err = httpdtest.LoaddataFromPostBody(backupContent, "0", "0", http.StatusOK)
- assert.NoError(t, err)
- shareGet, err = dataprovider.ShareExists(share.ShareID, user.Username)
- assert.NoError(t, err)
- assert.Equal(t, share, shareGet)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestLoaddataFromPostBody(t *testing.T) {
- mappedPath := filepath.Join(os.TempDir(), "restored_folder")
- folderName := filepath.Base(mappedPath)
- role := getTestRole()
- role.ID = 1
- role.Name = "test_restored_role"
- group := getTestGroup()
- group.ID = 1
- group.Name = "test_group_restored"
- user := getTestUser()
- user.ID = 1
- user.Username = "test_user_restored"
- user.Groups = []sdk.GroupMapping{
- {
- Name: group.Name,
- Type: sdk.GroupTypePrimary,
- },
- }
- user.Role = role.Name
- admin := getTestAdmin()
- admin.ID = 1
- admin.Username = "test_admin_restored"
- admin.Permissions = []string{dataprovider.PermAdminAddUsers, dataprovider.PermAdminChangeUsers,
- dataprovider.PermAdminDeleteUsers, dataprovider.PermAdminViewUsers}
- admin.Role = role.Name
- backupData := dataprovider.BackupData{}
- backupData.Users = append(backupData.Users, user)
- backupData.Groups = append(backupData.Groups, group)
- backupData.Admins = append(backupData.Admins, admin)
- backupData.Roles = append(backupData.Roles, role)
- backupData.Folders = []vfs.BaseVirtualFolder{
- {
- Name: folderName,
- MappedPath: mappedPath,
- UsedQuotaSize: 123,
- UsedQuotaFiles: 456,
- LastQuotaUpdate: 789,
- Users: []string{"user"},
- },
- {
- Name: folderName,
- MappedPath: mappedPath + "1",
- },
- }
- backupData.APIKeys = append(backupData.APIKeys, dataprovider.APIKey{})
- backupData.Shares = append(backupData.Shares, dataprovider.Share{})
- backupContent, err := json.Marshal(backupData)
- assert.NoError(t, err)
- _, _, err = httpdtest.LoaddataFromPostBody(nil, "0", "0", http.StatusBadRequest)
- assert.NoError(t, err)
- _, _, err = httpdtest.LoaddataFromPostBody(backupContent, "a", "0", http.StatusBadRequest)
- assert.NoError(t, err)
- _, _, err = httpdtest.LoaddataFromPostBody([]byte("invalid content"), "0", "0", http.StatusBadRequest)
- assert.NoError(t, err)
- _, _, err = httpdtest.LoaddataFromPostBody(backupContent, "0", "0", http.StatusInternalServerError)
- assert.NoError(t, err)
- keyID := util.GenerateUniqueID()
- backupData.APIKeys = []dataprovider.APIKey{
- {
- Name: "test key",
- Scope: dataprovider.APIKeyScopeAdmin,
- KeyID: keyID,
- Key: fmt.Sprintf("%v.%v", util.GenerateUniqueID(), util.GenerateUniqueID()),
- },
- }
- backupData.Shares = []dataprovider.Share{
- {
- ShareID: keyID,
- Name: keyID,
- Scope: dataprovider.ShareScopeWrite,
- Paths: []string{"/"},
- Username: user.Username,
- },
- }
- backupContent, err = json.Marshal(backupData)
- assert.NoError(t, err)
- _, resp, err := httpdtest.LoaddataFromPostBody(backupContent, "0", "0", http.StatusOK)
- assert.NoError(t, err, string(resp))
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Name, user.Role)
- if assert.Len(t, user.Groups, 1) {
- assert.Equal(t, sdk.GroupTypePrimary, user.Groups[0].Type)
- assert.Equal(t, group.Name, user.Groups[0].Name)
- }
- role, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, role.Admins, 1)
- assert.Len(t, role.Users, 1)
- _, err = dataprovider.ShareExists(keyID, user.Username)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- group, _, err = httpdtest.GetGroupByName(group.Name, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group, http.StatusOK)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Name, admin.Role)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- role, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, role.Admins, 0)
- assert.Len(t, role.Users, 0)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- apiKey, _, err := httpdtest.GetAPIKeyByID(keyID, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAPIKey(apiKey, http.StatusOK)
- assert.NoError(t, err)
- folder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath+"1", folder.MappedPath)
- assert.Equal(t, int64(123), folder.UsedQuotaSize)
- assert.Equal(t, 456, folder.UsedQuotaFiles)
- assert.Equal(t, int64(789), folder.LastQuotaUpdate)
- assert.Len(t, folder.Users, 0)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestLoaddata(t *testing.T) {
- mappedPath := filepath.Join(os.TempDir(), "restored_folder")
- folderName := filepath.Base(mappedPath)
- folderDesc := "restored folder desc"
- user := getTestUser()
- user.ID = 1
- user.Username = "test_user_restore"
- user.VirtualFolders = append(user.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName,
- },
- VirtualPath: "/vuserpath",
- })
- group := getTestGroup()
- group.ID = 1
- group.Name = "test_group_restore"
- group.VirtualFolders = append(group.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName,
- },
- VirtualPath: "/vgrouppath",
- })
- role := getTestRole()
- role.ID = 1
- role.Name = "test_role_restore"
- user.Groups = append(user.Groups, sdk.GroupMapping{
- Name: group.Name,
- Type: sdk.GroupTypePrimary,
- })
- admin := getTestAdmin()
- admin.ID = 1
- admin.Username = "test_admin_restore"
- admin.Groups = []dataprovider.AdminGroupMapping{
- {
- Name: group.Name,
- },
- }
- apiKey := dataprovider.APIKey{
- Name: util.GenerateUniqueID(),
- Scope: dataprovider.APIKeyScopeAdmin,
- KeyID: util.GenerateUniqueID(),
- Key: fmt.Sprintf("%v.%v", util.GenerateUniqueID(), util.GenerateUniqueID()),
- }
- share := dataprovider.Share{
- ShareID: util.GenerateUniqueID(),
- Name: util.GenerateUniqueID(),
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- Username: user.Username,
- }
- action := dataprovider.BaseEventAction{
- ID: 81,
- Name: "test restore action",
- Type: dataprovider.ActionTypeHTTP,
- Options: dataprovider.BaseEventActionOptions{
- HTTPConfig: dataprovider.EventActionHTTPConfig{
- Endpoint: "https://localhost:4567/action",
- Username: defaultUsername,
- Password: kms.NewPlainSecret(defaultPassword),
- Timeout: 10,
- SkipTLSVerify: true,
- Method: http.MethodPost,
- Body: `{"event":"{{Event}}","name":"{{Name}}"}`,
- },
- },
- }
- rule := dataprovider.EventRule{
- ID: 100,
- Name: "test rule restore",
- Description: "",
- Trigger: dataprovider.EventTriggerFsEvent,
- Conditions: dataprovider.EventConditions{
- FsEvents: []string{"download"},
- },
- Actions: []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: action.Name,
- },
- Order: 1,
- },
- },
- }
- backupData := dataprovider.BackupData{}
- backupData.Users = append(backupData.Users, user)
- backupData.Roles = append(backupData.Roles, role)
- backupData.Groups = append(backupData.Groups, group)
- backupData.Admins = append(backupData.Admins, admin)
- backupData.Folders = []vfs.BaseVirtualFolder{
- {
- Name: folderName,
- MappedPath: mappedPath + "1",
- UsedQuotaSize: 123,
- UsedQuotaFiles: 456,
- LastQuotaUpdate: 789,
- Users: []string{"user"},
- },
- {
- MappedPath: mappedPath,
- Name: folderName,
- Description: folderDesc,
- },
- }
- backupData.APIKeys = append(backupData.APIKeys, apiKey)
- backupData.Shares = append(backupData.Shares, share)
- backupData.EventActions = append(backupData.EventActions, action)
- backupData.EventRules = append(backupData.EventRules, rule)
- backupContent, err := json.Marshal(backupData)
- assert.NoError(t, err)
- backupFilePath := filepath.Join(backupsPath, "backup.json")
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "a", "", http.StatusBadRequest)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "", "a", http.StatusBadRequest)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata("backup.json", "1", "", http.StatusBadRequest)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath+"a", "1", "", http.StatusBadRequest)
- assert.NoError(t, err)
- if runtime.GOOS != osWindows {
- err = os.Chmod(backupFilePath, 0111)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "1", "", http.StatusForbidden)
- assert.NoError(t, err)
- err = os.Chmod(backupFilePath, 0644)
- assert.NoError(t, err)
- }
- // add objects from backup
- _, resp, err := httpdtest.Loaddata(backupFilePath, "1", "", http.StatusOK)
- assert.NoError(t, err, string(resp))
- // update from backup
- _, _, err = httpdtest.Loaddata(backupFilePath, "2", "", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, user.VirtualFolders, 1)
- assert.Len(t, user.Groups, 1)
- _, err = dataprovider.ShareExists(share.ShareID, user.Username)
- assert.NoError(t, err)
- role, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- group, _, err = httpdtest.GetGroupByName(group.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group.VirtualFolders, 1)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, admin.Groups, 1)
- apiKey, _, err = httpdtest.GetAPIKeyByID(apiKey.KeyID, http.StatusOK)
- assert.NoError(t, err)
- action, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- rule, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, rule.Actions, 1) {
- if assert.NotNil(t, rule.Actions[0].BaseEventAction.Options.HTTPConfig.Password) {
- assert.Equal(t, sdkkms.SecretStatusSecretBox, rule.Actions[0].BaseEventAction.Options.HTTPConfig.Password.GetStatus())
- assert.NotEmpty(t, rule.Actions[0].BaseEventAction.Options.HTTPConfig.Password.GetPayload())
- assert.Empty(t, rule.Actions[0].BaseEventAction.Options.HTTPConfig.Password.GetKey())
- assert.Empty(t, rule.Actions[0].BaseEventAction.Options.HTTPConfig.Password.GetAdditionalData())
- }
- }
- response, _, err := httpdtest.Dumpdata("", "1", "0", http.StatusOK)
- assert.NoError(t, err)
- var dumpedData dataprovider.BackupData
- data, err := json.Marshal(response)
- assert.NoError(t, err)
- err = json.Unmarshal(data, &dumpedData)
- assert.NoError(t, err)
- found := false
- if assert.GreaterOrEqual(t, len(dumpedData.Users), 1) {
- for _, u := range dumpedData.Users {
- if u.Username == user.Username {
- found = true
- assert.Equal(t, len(user.VirtualFolders), len(u.VirtualFolders))
- assert.Equal(t, len(user.Groups), len(u.Groups))
- }
- }
- }
- assert.True(t, found)
- found = false
- if assert.GreaterOrEqual(t, len(dumpedData.Admins), 1) {
- for _, a := range dumpedData.Admins {
- if a.Username == admin.Username {
- found = true
- assert.Equal(t, len(admin.Groups), len(a.Groups))
- }
- }
- }
- assert.True(t, found)
- if assert.Len(t, dumpedData.Groups, 1) {
- assert.Equal(t, len(group.VirtualFolders), len(dumpedData.Groups[0].VirtualFolders))
- }
- if assert.Len(t, dumpedData.EventActions, 1) {
- assert.Equal(t, action.Name, dumpedData.EventActions[0].Name)
- }
- if assert.Len(t, dumpedData.EventRules, 1) {
- assert.Equal(t, rule.Name, dumpedData.EventRules[0].Name)
- assert.Len(t, dumpedData.EventRules[0].Actions, 1)
- }
- found = false
- for _, r := range dumpedData.Roles {
- if r.Name == role.Name {
- found = true
- }
- }
- assert.True(t, found)
- folder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath, folder.MappedPath)
- assert.Equal(t, int64(123), folder.UsedQuotaSize)
- assert.Equal(t, 456, folder.UsedQuotaFiles)
- assert.Equal(t, int64(789), folder.LastQuotaUpdate)
- assert.Equal(t, folderDesc, folder.Description)
- assert.Len(t, folder.Users, 1)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAPIKey(apiKey, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventAction(action, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- err = os.Remove(backupFilePath)
- assert.NoError(t, err)
- err = createTestFile(backupFilePath, 10485761)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "1", "0", http.StatusBadRequest)
- assert.NoError(t, err)
- err = os.Remove(backupFilePath)
- assert.NoError(t, err)
- err = createTestFile(backupFilePath, 65535)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "1", "0", http.StatusBadRequest)
- assert.NoError(t, err)
- err = os.Remove(backupFilePath)
- assert.NoError(t, err)
- }
- func TestLoaddataMode(t *testing.T) {
- mappedPath := filepath.Join(os.TempDir(), "restored_fold")
- folderName := filepath.Base(mappedPath)
- role := getTestRole()
- role.ID = 1
- role.Name = "test_role_load"
- role.Description = ""
- user := getTestUser()
- user.ID = 1
- user.Username = "test_user_restore"
- user.Role = role.Name
- group := getTestGroup()
- group.ID = 1
- group.Name = "test_group_restore"
- user.Groups = []sdk.GroupMapping{
- {
- Name: group.Name,
- Type: sdk.GroupTypePrimary,
- },
- }
- admin := getTestAdmin()
- admin.ID = 1
- admin.Username = "test_admin_restore"
- apiKey := dataprovider.APIKey{
- Name: util.GenerateUniqueID(),
- Scope: dataprovider.APIKeyScopeAdmin,
- KeyID: util.GenerateUniqueID(),
- Key: fmt.Sprintf("%v.%v", util.GenerateUniqueID(), util.GenerateUniqueID()),
- Description: "desc",
- }
- share := dataprovider.Share{
- ShareID: util.GenerateUniqueID(),
- Name: util.GenerateUniqueID(),
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- Username: user.Username,
- }
- action := dataprovider.BaseEventAction{
- ID: 81,
- Name: "test restore action data mode",
- Description: "action desc",
- Type: dataprovider.ActionTypeHTTP,
- Options: dataprovider.BaseEventActionOptions{
- HTTPConfig: dataprovider.EventActionHTTPConfig{
- Endpoint: "https://localhost:4567/mode",
- Username: defaultUsername,
- Password: kms.NewPlainSecret(defaultPassword),
- Timeout: 10,
- SkipTLSVerify: true,
- Method: http.MethodPost,
- Body: `{"event":"{{Event}}","name":"{{Name}}"}`,
- },
- },
- }
- rule := dataprovider.EventRule{
- ID: 100,
- Name: "test rule restore data mode",
- Description: "rule desc",
- Trigger: dataprovider.EventTriggerFsEvent,
- Conditions: dataprovider.EventConditions{
- FsEvents: []string{"mkdir"},
- },
- Actions: []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: action.Name,
- },
- Order: 1,
- },
- },
- }
- backupData := dataprovider.BackupData{}
- backupData.Users = append(backupData.Users, user)
- backupData.Groups = append(backupData.Groups, group)
- backupData.Admins = append(backupData.Admins, admin)
- backupData.EventActions = append(backupData.EventActions, action)
- backupData.EventRules = append(backupData.EventRules, rule)
- backupData.Roles = append(backupData.Roles, role)
- backupData.Folders = []vfs.BaseVirtualFolder{
- {
- Name: folderName,
- MappedPath: mappedPath,
- UsedQuotaSize: 123,
- UsedQuotaFiles: 456,
- LastQuotaUpdate: 789,
- Users: []string{"user"},
- },
- {
- MappedPath: mappedPath + "1",
- Name: folderName,
- },
- }
- backupData.APIKeys = append(backupData.APIKeys, apiKey)
- backupData.Shares = append(backupData.Shares, share)
- backupContent, _ := json.Marshal(backupData)
- backupFilePath := filepath.Join(backupsPath, "backup.json")
- err := os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- _, _, err = httpdtest.Loaddata(backupFilePath, "0", "0", http.StatusOK)
- assert.NoError(t, err)
- folder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath+"1", folder.MappedPath)
- assert.Equal(t, int64(123), folder.UsedQuotaSize)
- assert.Equal(t, 456, folder.UsedQuotaFiles)
- assert.Equal(t, int64(789), folder.LastQuotaUpdate)
- assert.Len(t, folder.Users, 0)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Name, user.Role)
- oldUploadBandwidth := user.UploadBandwidth
- user.UploadBandwidth = oldUploadBandwidth + 128
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- role, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, role.Users, 1)
- assert.Len(t, role.Admins, 0)
- assert.Empty(t, role.Description)
- role.Description = "role desc"
- _, _, err = httpdtest.UpdateRole(role, http.StatusOK)
- assert.NoError(t, err)
- role.Description = ""
- group, _, err = httpdtest.GetGroupByName(group.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, group.Users, 1)
- oldGroupDesc := group.Description
- group.Description = "new group description"
- group, _, err = httpdtest.UpdateGroup(group, http.StatusOK)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- oldInfo := admin.AdditionalInfo
- oldDesc := admin.Description
- admin.AdditionalInfo = "newInfo"
- admin.Description = "newDesc"
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- apiKey, _, err = httpdtest.GetAPIKeyByID(apiKey.KeyID, http.StatusOK)
- assert.NoError(t, err)
- oldAPIKeyDesc := apiKey.Description
- apiKey.ExpiresAt = util.GetTimeAsMsSinceEpoch(time.Now())
- apiKey.Description = "new desc"
- apiKey, _, err = httpdtest.UpdateAPIKey(apiKey, http.StatusOK)
- assert.NoError(t, err)
- share.Description = "test desc"
- err = dataprovider.UpdateShare(&share, "", "", "")
- assert.NoError(t, err)
- action, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- oldActionDesc := action.Description
- action.Description = "new action description"
- action, _, err = httpdtest.UpdateEventAction(action, http.StatusOK)
- assert.NoError(t, err)
- rule, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
- assert.NoError(t, err)
- oldRuleDesc := rule.Description
- rule.Description = "new rule description"
- rule, _, err = httpdtest.UpdateEventRule(rule, http.StatusOK)
- assert.NoError(t, err)
- backupData.Folders = []vfs.BaseVirtualFolder{
- {
- MappedPath: mappedPath,
- Name: folderName,
- },
- }
- _, _, err = httpdtest.Loaddata(backupFilePath, "0", "1", http.StatusOK)
- assert.NoError(t, err)
- group, _, err = httpdtest.GetGroupByName(group.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.NotEqual(t, oldGroupDesc, group.Description)
- folder, _, err = httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath+"1", folder.MappedPath)
- assert.Equal(t, int64(123), folder.UsedQuotaSize)
- assert.Equal(t, 456, folder.UsedQuotaFiles)
- assert.Equal(t, int64(789), folder.LastQuotaUpdate)
- assert.Len(t, folder.Users, 0)
- action, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.NotEqual(t, oldActionDesc, action.Description)
- rule, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.NotEqual(t, oldRuleDesc, rule.Description)
- c := common.NewBaseConnection("connID", common.ProtocolFTP, "", "", user)
- fakeConn := &fakeConnection{
- BaseConnection: c,
- }
- err = common.Connections.Add(fakeConn)
- assert.NoError(t, err)
- assert.Len(t, common.Connections.GetStats(""), 1)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.NotEqual(t, oldUploadBandwidth, user.UploadBandwidth)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.NotEqual(t, oldInfo, admin.AdditionalInfo)
- assert.NotEqual(t, oldDesc, admin.Description)
- apiKey, _, err = httpdtest.GetAPIKeyByID(apiKey.KeyID, http.StatusOK)
- assert.NoError(t, err)
- assert.NotEqual(t, int64(0), apiKey.ExpiresAt)
- assert.NotEqual(t, oldAPIKeyDesc, apiKey.Description)
- share, err = dataprovider.ShareExists(share.ShareID, user.Username)
- assert.NoError(t, err)
- assert.NotEmpty(t, share.Description)
- role, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, role.Users, 1)
- assert.Len(t, role.Admins, 0)
- assert.NotEmpty(t, role.Description)
- _, _, err = httpdtest.Loaddata(backupFilePath, "0", "2", http.StatusOK)
- assert.NoError(t, err)
- // mode 2 will update the user and close the previous connection
- assert.Len(t, common.Connections.GetStats(""), 0)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, oldUploadBandwidth, user.UploadBandwidth)
- // the group is referenced
- _, err = httpdtest.RemoveGroup(group, http.StatusBadRequest)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAPIKey(apiKey, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventAction(action, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- err = os.Remove(backupFilePath)
- assert.NoError(t, err)
- }
- func TestRateLimiter(t *testing.T) {
- oldConfig := config.GetCommonConfig()
- cfg := config.GetCommonConfig()
- cfg.RateLimitersConfig = []common.RateLimiterConfig{
- {
- Average: 1,
- Period: 1000,
- Burst: 1,
- Type: 1,
- Protocols: []string{common.ProtocolHTTP},
- },
- }
- err := common.Initialize(cfg, 0)
- assert.NoError(t, err)
- client := &http.Client{
- Timeout: 5 * time.Second,
- }
- resp, err := client.Get(httpBaseURL + healthzPath)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- resp, err = client.Get(httpBaseURL + healthzPath)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusTooManyRequests, resp.StatusCode)
- assert.Equal(t, "1", resp.Header.Get("Retry-After"))
- assert.NotEmpty(t, resp.Header.Get("X-Retry-In"))
- err = resp.Body.Close()
- assert.NoError(t, err)
- resp, err = client.Get(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusTooManyRequests, resp.StatusCode)
- assert.Equal(t, "1", resp.Header.Get("Retry-After"))
- assert.NotEmpty(t, resp.Header.Get("X-Retry-In"))
- err = resp.Body.Close()
- assert.NoError(t, err)
- resp, err = client.Get(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusTooManyRequests, resp.StatusCode)
- assert.Equal(t, "1", resp.Header.Get("Retry-After"))
- assert.NotEmpty(t, resp.Header.Get("X-Retry-In"))
- err = resp.Body.Close()
- assert.NoError(t, err)
- err = common.Initialize(oldConfig, 0)
- assert.NoError(t, err)
- }
- func TestHTTPSConnection(t *testing.T) {
- client := &http.Client{
- Timeout: 5 * time.Second,
- }
- resp, err := client.Get("https://localhost:8443" + healthzPath)
- if assert.Error(t, err) {
- if !strings.Contains(err.Error(), "certificate is not valid") &&
- !strings.Contains(err.Error(), "certificate signed by unknown authority") &&
- !strings.Contains(err.Error(), "certificate is not standards compliant") {
- assert.Fail(t, err.Error())
- }
- } else {
- resp.Body.Close()
- }
- }
- // test using mock http server
- func TestBasicUserHandlingMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, err := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- user.MaxSessions = 10
- user.UploadBandwidth = 128
- user.Permissions["/"] = []string{dataprovider.PermAny, dataprovider.PermDelete, dataprovider.PermDownload}
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPut, userPath+"/"+user.Username, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, userPath+"/"+user.Username, nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updatedUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updatedUser)
- assert.NoError(t, err)
- assert.Equal(t, user.MaxSessions, updatedUser.MaxSessions)
- assert.Equal(t, user.UploadBandwidth, updatedUser.UploadBandwidth)
- assert.Equal(t, 1, len(updatedUser.Permissions["/"]))
- assert.True(t, util.Contains(updatedUser.Permissions["/"], dataprovider.PermAny))
- req, _ = http.NewRequest(http.MethodDelete, userPath+"/"+user.Username, nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestAddUserNoUsernameMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- user.Username = ""
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestAddUserInvalidHomeDirMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- user.HomeDir = "relative_path"
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestAddUserInvalidPermsMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- user.Permissions["/"] = []string{}
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestAddFolderInvalidJsonMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer([]byte("invalid json")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestAddEventRuleInvalidJsonMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, eventActionsPath, bytes.NewBuffer([]byte("invalid json")))
- require.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodPost, eventRulesPath, bytes.NewBuffer([]byte("invalid json")))
- require.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestAddRoleInvalidJsonMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, rolesPath, bytes.NewBuffer([]byte("{")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestRoleErrorsMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- reqBody := bytes.NewBuffer([]byte("{"))
- req, err := http.NewRequest(http.MethodGet, rolesPath+"?limit=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- role, _, err := httpdtest.AddRole(getTestRole(), http.StatusCreated)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, path.Join(rolesPath, role.Name), reqBody)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodPut, path.Join(rolesPath, "missing_role"), reqBody)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveRole(role, http.StatusNotFound)
- assert.NoError(t, err)
- }
- func TestEventRuleErrorsMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- reqBody := bytes.NewBuffer([]byte("invalid json body"))
- req, err := http.NewRequest(http.MethodGet, eventActionsPath+"?limit=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, eventRulesPath+"?limit=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- a := dataprovider.BaseEventAction{
- Name: "action name",
- Description: "test description",
- Type: dataprovider.ActionTypeBackup,
- Options: dataprovider.BaseEventActionOptions{},
- }
- action, _, err := httpdtest.AddEventAction(a, http.StatusCreated)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, path.Join(eventActionsPath, action.Name), reqBody)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- r := dataprovider.EventRule{
- Name: "test event rule",
- Trigger: dataprovider.EventTriggerSchedule,
- Conditions: dataprovider.EventConditions{
- Schedules: []dataprovider.Schedule{
- {
- Hours: "2",
- DayOfWeek: "*",
- DayOfMonth: "*",
- Month: "*",
- },
- },
- },
- Actions: []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: action.Name,
- },
- Order: 1,
- },
- },
- }
- rule, _, err := httpdtest.AddEventRule(r, http.StatusCreated)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, path.Join(eventRulesPath, rule.Name), reqBody)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- rule.Actions[0].Name = "misssing action name"
- asJSON, err := json.Marshal(rule)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, path.Join(eventRulesPath, rule.Name), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- _, err = httpdtest.RemoveEventRule(rule, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveEventAction(action, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestGroupErrorsMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- reqBody := bytes.NewBuffer([]byte("not a json string"))
- req, err := http.NewRequest(http.MethodPost, groupPath, reqBody)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, groupPath+"?limit=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- group, _, err := httpdtest.AddGroup(getTestGroup(), http.StatusCreated)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, path.Join(groupPath, group.Name), reqBody)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- _, err = httpdtest.RemoveGroup(group, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUpdateFolderInvalidJsonMock(t *testing.T) {
- folder := vfs.BaseVirtualFolder{
- Name: "name",
- MappedPath: filepath.Clean(os.TempDir()),
- }
- folder, resp, err := httpdtest.AddFolder(folder, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPut, path.Join(folderPath, folder.Name), bytes.NewBuffer([]byte("not a json")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAddUserInvalidJsonMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer([]byte("invalid json")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestAddAdminInvalidJsonMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, adminPath, bytes.NewBuffer([]byte("...")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestAddAdminNoPasswordMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- admin := getTestAdmin()
- admin.Password = ""
- asJSON, err := json.Marshal(admin)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, adminPath, bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "please set a password")
- }
- func TestAdminTwoFactorLogin(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- // enable two factor authentication
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], admin.Username)
- assert.NoError(t, err)
- altToken, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- adminTOTPConfig := dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- }
- asJSON, err := json.Marshal(adminTOTPConfig)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- req, err = http.NewRequest(http.MethodGet, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var recCodes []recoveryCode
- err = json.Unmarshal(rr.Body.Bytes(), &recCodes)
- assert.NoError(t, err)
- assert.Len(t, recCodes, 12)
- admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, admin.Filters.RecoveryCodes, 12)
- for _, c := range admin.Filters.RecoveryCodes {
- assert.Empty(t, c.Secret.GetAdditionalData())
- assert.Empty(t, c.Secret.GetKey())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, c.Secret.GetStatus())
- assert.NotEmpty(t, c.Secret.GetPayload())
- }
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webAdminTwoFactorPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminTwoFactorRecoveryPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := getLoginForm(altAdminUsername, altAdminPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminTwoFactorPath, rr.Header().Get("Location"))
- cookie, err := getCookieFromResponse(rr)
- assert.NoError(t, err)
- // without a cookie
- req, err = http.NewRequest(http.MethodGet, webAdminTwoFactorRecoveryPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminTwoFactorPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminTwoFactorRecoveryPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // any other page will be redirected to the two factor auth page
- req, err = http.NewRequest(http.MethodGet, webUsersPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webAdminTwoFactorPath, rr.Header().Get("Location"))
- // a partial token cannot be used for user pages
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- passcode, err := generateTOTPPasscode(secret)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set("passcode", passcode)
- // no csrf
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- form.Set("passcode", "invalid_passcode")
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid authentication code")
- form.Set("passcode", "")
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid credentials")
- form.Set("passcode", passcode)
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webUsersPath, rr.Header().Get("Location"))
- // the same cookie cannot be reused
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusNotFound, rr.Code)
- // get a new cookie and login using a recovery code
- form = getLoginForm(altAdminUsername, altAdminPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminTwoFactorPath, rr.Header().Get("Location"))
- cookie, err = getCookieFromResponse(rr)
- assert.NoError(t, err)
- form = make(url.Values)
- recoveryCode := recCodes[0].Code
- form.Set("recovery_code", recoveryCode)
- // no csrf
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- form.Set("recovery_code", "")
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid credentials")
- form.Set("recovery_code", recoveryCode)
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webUsersPath, rr.Header().Get("Location"))
- authenticatedCookie, err := getCookieFromResponse(rr)
- assert.NoError(t, err)
- //render MFA page
- req, err = http.NewRequest(http.MethodGet, webAdminMFAPath, nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, authenticatedCookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // check that the recovery code was marked as used
- req, err = http.NewRequest(http.MethodGet, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- recCodes = nil
- err = json.Unmarshal(rr.Body.Bytes(), &recCodes)
- assert.NoError(t, err)
- assert.Len(t, recCodes, 12)
- found := false
- for _, rc := range recCodes {
- if rc.Code == recoveryCode {
- found = true
- assert.True(t, rc.Used)
- } else {
- assert.False(t, rc.Used)
- }
- }
- assert.True(t, found)
- // the same recovery code cannot be reused
- form = getLoginForm(altAdminUsername, altAdminPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminTwoFactorPath, rr.Header().Get("Location"))
- cookie, err = getCookieFromResponse(rr)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set("recovery_code", recoveryCode)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "This recovery code was already used")
- form.Set("recovery_code", "invalid_recovery_code")
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid recovery code")
- form = getLoginForm(altAdminUsername, altAdminPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminTwoFactorPath, rr.Header().Get("Location"))
- cookie, err = getCookieFromResponse(rr)
- assert.NoError(t, err)
- // disable TOTP
- req, err = http.NewRequest(http.MethodPut, adminPath+"/"+altAdminUsername+"/2fa/disable", nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form = make(url.Values)
- form.Set("recovery_code", recoveryCode)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Two factory authentication is not enabled")
- form.Set("passcode", passcode)
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Two factory authentication is not enabled")
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid credentials")
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid credentials")
- req, err = http.NewRequest(http.MethodGet, webAdminMFAPath, nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, authenticatedCookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- }
- func TestAdminTOTP(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- // TOTPConfig will be ignored on add
- admin.Filters.TOTPConfig = dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: "config",
- Secret: kms.NewEmptySecret(),
- }
- asJSON, err := json.Marshal(admin)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, adminPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.False(t, admin.Filters.TOTPConfig.Enabled)
- assert.Len(t, admin.Filters.RecoveryCodes, 0)
- altToken, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, adminTOTPConfigsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var configs []mfa.TOTPConfig
- err = json.Unmarshal(rr.Body.Bytes(), &configs)
- assert.NoError(t, err, rr.Body.String())
- assert.Len(t, configs, len(mfa.GetAvailableTOTPConfigs()))
- totpConfig := configs[0]
- totpReq := generateTOTPRequest{
- ConfigName: totpConfig.Name,
- }
- asJSON, err = json.Marshal(totpReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, adminTOTPGeneratePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var totpGenResp generateTOTPResponse
- err = json.Unmarshal(rr.Body.Bytes(), &totpGenResp)
- assert.NoError(t, err)
- assert.NotEmpty(t, totpGenResp.Secret)
- assert.NotEmpty(t, totpGenResp.QRCode)
- passcode, err := generateTOTPPasscode(totpGenResp.Secret)
- assert.NoError(t, err)
- validateReq := validateTOTPRequest{
- ConfigName: totpGenResp.ConfigName,
- Passcode: passcode,
- Secret: totpGenResp.Secret,
- }
- asJSON, err = json.Marshal(validateReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, adminTOTPValidatePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // the same passcode cannot be reused
- req, err = http.NewRequest(http.MethodPost, adminTOTPValidatePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "this passcode was already used")
- adminTOTPConfig := dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: totpGenResp.ConfigName,
- Secret: kms.NewPlainSecret(totpGenResp.Secret),
- }
- asJSON, err = json.Marshal(adminTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- assert.Equal(t, totpGenResp.ConfigName, admin.Filters.TOTPConfig.ConfigName)
- assert.Empty(t, admin.Filters.TOTPConfig.Secret.GetKey())
- assert.Empty(t, admin.Filters.TOTPConfig.Secret.GetAdditionalData())
- assert.NotEmpty(t, admin.Filters.TOTPConfig.Secret.GetPayload())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, admin.Filters.TOTPConfig.Secret.GetStatus())
- admin.Filters.TOTPConfig = dataprovider.AdminTOTPConfig{
- Enabled: false,
- ConfigName: util.GenerateUniqueID(),
- Secret: kms.NewEmptySecret(),
- }
- admin.Filters.RecoveryCodes = []dataprovider.RecoveryCode{
- {
- Secret: kms.NewEmptySecret(),
- },
- }
- admin, resp, err := httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err, string(resp))
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- assert.Len(t, admin.Filters.RecoveryCodes, 12)
- // if we use token we cannot get or generate recovery codes
- req, err = http.NewRequest(http.MethodGet, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodPost, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- // now the same but with altToken
- req, err = http.NewRequest(http.MethodGet, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var recCodes []recoveryCode
- err = json.Unmarshal(rr.Body.Bytes(), &recCodes)
- assert.NoError(t, err)
- assert.Len(t, recCodes, 12)
- // regenerate recovery codes
- req, err = http.NewRequest(http.MethodPost, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // check that recovery codes are different
- req, err = http.NewRequest(http.MethodGet, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var newRecCodes []recoveryCode
- err = json.Unmarshal(rr.Body.Bytes(), &newRecCodes)
- assert.NoError(t, err)
- assert.Len(t, newRecCodes, 12)
- assert.NotEqual(t, recCodes, newRecCodes)
- // disable 2FA, the update admin API should not work
- admin.Filters.TOTPConfig.Enabled = false
- admin.Filters.RecoveryCodes = nil
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, altAdminUsername, admin.Username)
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- assert.Len(t, admin.Filters.RecoveryCodes, 12)
- // use the dedicated API
- req, err = http.NewRequest(http.MethodPut, adminPath+"/"+altAdminUsername+"/2fa/disable", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.False(t, admin.Filters.TOTPConfig.Enabled)
- assert.Len(t, admin.Filters.RecoveryCodes, 0)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(adminPath, altAdminUsername), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodPut, adminPath+"/"+altAdminUsername+"/2fa/disable", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, admin2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestChangeAdminPwdInvalidJsonMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPut, adminPwdPath, bytes.NewBuffer([]byte("{")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestMFAPermission(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webClientMFAPath, nil)
- assert.NoError(t, err)
- req.RequestURI = webClientMFAPath
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user.Filters.WebClient = []string{sdk.WebClientMFADisabled}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientMFAPath, nil)
- assert.NoError(t, err)
- req.RequestURI = webClientMFAPath
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebUserTwoFactorLogin(t *testing.T) {
- u := getTestUser()
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- // enable two factor authentication
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- adminToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- userTOTPConfig := dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- Protocols: []string{common.ProtocolHTTP},
- }
- asJSON, err := json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var recCodes []recoveryCode
- err = json.Unmarshal(rr.Body.Bytes(), &recCodes)
- assert.NoError(t, err)
- assert.Len(t, recCodes, 12)
- user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, user.Filters.RecoveryCodes, 12)
- for _, c := range user.Filters.RecoveryCodes {
- assert.Empty(t, c.Secret.GetAdditionalData())
- assert.Empty(t, c.Secret.GetKey())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, c.Secret.GetStatus())
- assert.NotEmpty(t, c.Secret.GetPayload())
- }
- req, err = http.NewRequest(http.MethodGet, webClientTwoFactorPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, webClientTwoFactorRecoveryPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- form := getLoginForm(defaultUsername, defaultPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientTwoFactorPath, rr.Header().Get("Location"))
- cookie, err := getCookieFromResponse(rr)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientTwoFactorPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // without a cookie
- req, err = http.NewRequest(http.MethodGet, webClientTwoFactorPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, webClientTwoFactorRecoveryPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // any other page will be redirected to the two factor auth page
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientTwoFactorPath, rr.Header().Get("Location"))
- // a partial token cannot be used for admin pages
- req, err = http.NewRequest(http.MethodGet, webUsersPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- passcode, err := generateTOTPPasscode(secret)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set("passcode", passcode)
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- form.Set("passcode", "invalid_user_passcode")
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid authentication code")
- form.Set("passcode", "")
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid credentials")
- form.Set("passcode", passcode)
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientFilesPath, rr.Header().Get("Location"))
- // the same cookie cannot be reused
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusNotFound, rr.Code)
- // get a new cookie and login using a recovery code
- form = getLoginForm(defaultUsername, defaultPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientTwoFactorPath, rr.Header().Get("Location"))
- cookie, err = getCookieFromResponse(rr)
- assert.NoError(t, err)
- form = make(url.Values)
- recoveryCode := recCodes[0].Code
- form.Set("recovery_code", recoveryCode)
- // no csrf
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- form.Set("recovery_code", "")
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid credentials")
- form.Set("recovery_code", recoveryCode)
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientFilesPath, rr.Header().Get("Location"))
- authenticatedCookie, err := getCookieFromResponse(rr)
- assert.NoError(t, err)
- //render MFA page
- req, err = http.NewRequest(http.MethodGet, webClientMFAPath, nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, authenticatedCookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // check that the recovery code was marked as used
- req, err = http.NewRequest(http.MethodGet, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- recCodes = nil
- err = json.Unmarshal(rr.Body.Bytes(), &recCodes)
- assert.NoError(t, err)
- assert.Len(t, recCodes, 12)
- found := false
- for _, rc := range recCodes {
- if rc.Code == recoveryCode {
- found = true
- assert.True(t, rc.Used)
- } else {
- assert.False(t, rc.Used)
- }
- }
- assert.True(t, found)
- // the same recovery code cannot be reused
- form = getLoginForm(defaultUsername, defaultPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientTwoFactorPath, rr.Header().Get("Location"))
- cookie, err = getCookieFromResponse(rr)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set("recovery_code", recoveryCode)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "This recovery code was already used")
- form.Set("recovery_code", "invalid_user_recovery_code")
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid recovery code")
- form = getLoginForm(defaultUsername, defaultPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientTwoFactorPath, rr.Header().Get("Location"))
- cookie, err = getCookieFromResponse(rr)
- assert.NoError(t, err)
- // disable TOTP
- req, err = http.NewRequest(http.MethodPut, userPath+"/"+user.Username+"/2fa/disable", nil)
- assert.NoError(t, err)
- setBearerForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form = make(url.Values)
- form.Set("recovery_code", recoveryCode)
- form.Set("passcode", passcode)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Two factory authentication is not enabled")
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Two factory authentication is not enabled")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid credentials")
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid credentials")
- req, err = http.NewRequest(http.MethodGet, webClientMFAPath, nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, authenticatedCookie)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- }
- func TestSearchEvents(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, fsEventsPath+"?limit=10&order=ASC&fs_provider=0", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- events := make([]map[string]any, 0)
- err = json.Unmarshal(rr.Body.Bytes(), &events)
- assert.NoError(t, err)
- if assert.Len(t, events, 1) {
- ev := events[0]
- for _, field := range []string{"id", "timestamp", "action", "username", "fs_path", "status", "protocol",
- "ip", "session_id", "fs_provider", "bucket", "endpoint", "open_flags", "role", "instance_id"} {
- _, ok := ev[field]
- assert.True(t, ok, field)
- }
- }
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?limit=10&order=ASC&role=role1", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- events = nil
- err = json.Unmarshal(rr.Body.Bytes(), &events)
- assert.NoError(t, err)
- assert.Len(t, events, 1)
- // CSV export
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?limit=10&order=ASC&csv_export=true", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "text/csv", rr.Header().Get("Content-Type"))
- // the test eventsearcher plugin returns error if start_timestamp < 0
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?start_timestamp=-1&end_timestamp=123456&statuses=1,2", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- // CSV export with error
- exportFunc := func() {
- defer func() {
- rcv := recover()
- assert.Equal(t, http.ErrAbortHandler, rcv)
- }()
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?start_timestamp=-1&csv_export=true", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- }
- exportFunc()
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?limit=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, providerEventsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- events = make([]map[string]any, 0)
- err = json.Unmarshal(rr.Body.Bytes(), &events)
- assert.NoError(t, err)
- if assert.Len(t, events, 1) {
- ev := events[0]
- for _, field := range []string{"id", "timestamp", "action", "username", "object_type", "object_name",
- "object_data", "role", "instance_id"} {
- _, ok := ev[field]
- assert.True(t, ok, field)
- }
- }
- req, err = http.NewRequest(http.MethodGet, providerEventsPath+"?omit_object_data=true", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- events = make([]map[string]any, 0)
- err = json.Unmarshal(rr.Body.Bytes(), &events)
- assert.NoError(t, err)
- if assert.Len(t, events, 1) {
- ev := events[0]
- field := "object_data"
- _, ok := ev[field]
- assert.False(t, ok, field)
- }
- // CSV export
- req, err = http.NewRequest(http.MethodGet, providerEventsPath+"?csv_export=true", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "text/csv", rr.Header().Get("Content-Type"))
- // the test eventsearcher plugin returns error if start_timestamp < 0
- req, err = http.NewRequest(http.MethodGet, providerEventsPath+"?start_timestamp=-1", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- // CSV export with error
- exportFunc = func() {
- defer func() {
- rcv := recover()
- assert.Equal(t, http.ErrAbortHandler, rcv)
- }()
- req, err = http.NewRequest(http.MethodGet, providerEventsPath+"?start_timestamp=-1&csv_export=true", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- }
- exportFunc()
- req, err = http.NewRequest(http.MethodGet, providerEventsPath+"?limit=2000", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?start_timestamp=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?end_timestamp=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?order=ASSC", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?statuses=a,b", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, fsEventsPath+"?fs_provider=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, webEventsPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestMFAErrors(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- assert.False(t, user.Filters.TOTPConfig.Enabled)
- userToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- adminToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- // invalid config name
- totpReq := generateTOTPRequest{
- ConfigName: "invalid config name",
- }
- asJSON, err := json.Marshal(totpReq)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userTOTPGeneratePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- // invalid JSON
- invalidJSON := []byte("not a JSON")
- req, err = http.NewRequest(http.MethodPost, userTOTPGeneratePath, bytes.NewBuffer(invalidJSON))
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(invalidJSON))
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(invalidJSON))
- assert.NoError(t, err)
- setBearerForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodPost, adminTOTPValidatePath, bytes.NewBuffer(invalidJSON))
- assert.NoError(t, err)
- setBearerForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- // invalid TOTP config name
- userTOTPConfig := dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: "missing name",
- Secret: kms.NewPlainSecret(xid.New().String()),
- Protocols: []string{common.ProtocolSSH},
- }
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "totp: config name")
- // invalid TOTP secret
- userTOTPConfig = dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: mfa.GetAvailableTOTPConfigNames()[0],
- Secret: nil,
- Protocols: []string{common.ProtocolSSH},
- }
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "totp: secret is mandatory")
- // no protocol
- userTOTPConfig = dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: mfa.GetAvailableTOTPConfigNames()[0],
- Secret: kms.NewPlainSecret(xid.New().String()),
- Protocols: nil,
- }
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "totp: specify at least one protocol")
- // invalid protocol
- userTOTPConfig = dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: mfa.GetAvailableTOTPConfigNames()[0],
- Secret: kms.NewPlainSecret(xid.New().String()),
- Protocols: []string{common.ProtocolWebDAV},
- }
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "totp: invalid protocol")
- adminTOTPConfig := dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: "",
- Secret: kms.NewPlainSecret("secret"),
- }
- asJSON, err = json.Marshal(adminTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "totp: config name is mandatory")
- adminTOTPConfig = dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: mfa.GetAvailableTOTPConfigNames()[0],
- Secret: nil,
- }
- asJSON, err = json.Marshal(adminTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "totp: secret is mandatory")
- // invalid TOTP secret status
- userTOTPConfig = dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: mfa.GetAvailableTOTPConfigNames()[0],
- Secret: kms.NewSecret(sdkkms.SecretStatusRedacted, "", "", ""),
- Protocols: []string{common.ProtocolSSH},
- }
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- // previous secret will be preserved and we have no secret saved
- assert.Contains(t, rr.Body.String(), "totp: secret is mandatory")
- req, err = http.NewRequest(http.MethodPost, adminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "totp: secret is mandatory")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestMFAInvalidSecret(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- userToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- user.Password = defaultPassword
- user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: mfa.GetAvailableTOTPConfigNames()[0],
- Secret: kms.NewSecret(sdkkms.SecretStatusSecretBox, "payload", "key", user.Username),
- Protocols: []string{common.ProtocolSSH, common.ProtocolHTTP},
- }
- user.Filters.RecoveryCodes = append(user.Filters.RecoveryCodes, dataprovider.RecoveryCode{
- Used: false,
- Secret: kms.NewSecret(sdkkms.SecretStatusSecretBox, "payload", "key", user.Username),
- })
- err = dataprovider.UpdateUser(&user, "", "", "")
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, userToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- assert.Contains(t, rr.Body.String(), "Unable to decrypt recovery codes")
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- form := getLoginForm(defaultUsername, defaultPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webClientTwoFactorPath, rr.Header().Get("Location"))
- cookie, err := getCookieFromResponse(rr)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("passcode", "123456")
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("recovery_code", "RC-123456")
- req, err = http.NewRequest(http.MethodPost, webClientTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userTokenPath), nil)
- assert.NoError(t, err)
- req.Header.Set("X-SFTPGO-OTP", "authcode")
- req.SetBasicAuth(defaultUsername, defaultPassword)
- resp, err := httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin, _, err = httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- admin.Password = altAdminPassword
- admin.Filters.TOTPConfig = dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: mfa.GetAvailableTOTPConfigNames()[0],
- Secret: kms.NewSecret(sdkkms.SecretStatusSecretBox, "payload", "key", user.Username),
- }
- admin.Filters.RecoveryCodes = append(user.Filters.RecoveryCodes, dataprovider.RecoveryCode{
- Used: false,
- Secret: kms.NewSecret(sdkkms.SecretStatusSecretBox, "payload", "key", user.Username),
- })
- err = dataprovider.UpdateAdmin(&admin, "", "", "")
- assert.NoError(t, err)
- csrfToken, err = getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form = getLoginForm(altAdminUsername, altAdminPassword, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.Equal(t, webAdminTwoFactorPath, rr.Header().Get("Location"))
- cookie, err = getCookieFromResponse(rr)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("passcode", "123456")
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("recovery_code", "RC-123456")
- req, err = http.NewRequest(http.MethodPost, webAdminTwoFactorRecoveryPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- setJWTCookieForReq(req, cookie)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusInternalServerError, rr.Code)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, tokenPath), nil)
- assert.NoError(t, err)
- req.Header.Set("X-SFTPGO-OTP", "auth-code")
- req.SetBasicAuth(altAdminUsername, altAdminPassword)
- resp, err = httpclient.GetHTTPClient().Do(req)
- assert.NoError(t, err)
- assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
- err = resp.Body.Close()
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestWebUserTOTP(t *testing.T) {
- u := getTestUser()
- // TOTPConfig will be ignored on add
- u.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: "",
- Secret: kms.NewEmptySecret(),
- Protocols: []string{common.ProtocolSSH},
- }
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- assert.False(t, user.Filters.TOTPConfig.Enabled)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, userTOTPConfigsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var configs []mfa.TOTPConfig
- err = json.Unmarshal(rr.Body.Bytes(), &configs)
- assert.NoError(t, err, rr.Body.String())
- assert.Len(t, configs, len(mfa.GetAvailableTOTPConfigs()))
- totpConfig := configs[0]
- totpReq := generateTOTPRequest{
- ConfigName: totpConfig.Name,
- }
- asJSON, err := json.Marshal(totpReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPGeneratePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var totpGenResp generateTOTPResponse
- err = json.Unmarshal(rr.Body.Bytes(), &totpGenResp)
- assert.NoError(t, err)
- assert.NotEmpty(t, totpGenResp.Secret)
- assert.NotEmpty(t, totpGenResp.QRCode)
- passcode, err := generateTOTPPasscode(totpGenResp.Secret)
- assert.NoError(t, err)
- validateReq := validateTOTPRequest{
- ConfigName: totpGenResp.ConfigName,
- Passcode: passcode,
- Secret: totpGenResp.Secret,
- }
- asJSON, err = json.Marshal(validateReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPValidatePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // the same passcode cannot be reused
- req, err = http.NewRequest(http.MethodPost, userTOTPValidatePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "this passcode was already used")
- userTOTPConfig := dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: totpGenResp.ConfigName,
- Secret: kms.NewPlainSecret(totpGenResp.Secret),
- Protocols: []string{common.ProtocolSSH},
- }
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- totpCfg := user.Filters.TOTPConfig
- assert.True(t, totpCfg.Enabled)
- secretPayload := totpCfg.Secret.GetPayload()
- assert.Equal(t, totpGenResp.ConfigName, totpCfg.ConfigName)
- assert.Empty(t, totpCfg.Secret.GetKey())
- assert.Empty(t, totpCfg.Secret.GetAdditionalData())
- assert.NotEmpty(t, secretPayload)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, totpCfg.Secret.GetStatus())
- assert.Len(t, totpCfg.Protocols, 1)
- assert.Contains(t, totpCfg.Protocols, common.ProtocolSSH)
- // update protocols only
- userTOTPConfig = dataprovider.UserTOTPConfig{
- Protocols: []string{common.ProtocolSSH, common.ProtocolFTP},
- Secret: kms.NewEmptySecret(),
- }
- asJSON, err = json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // update the user, TOTP should not be affected
- user.Filters.TOTPConfig = dataprovider.UserTOTPConfig{
- Enabled: false,
- Secret: kms.NewEmptySecret(),
- }
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, user.Filters.TOTPConfig.Enabled)
- assert.Equal(t, totpCfg.ConfigName, user.Filters.TOTPConfig.ConfigName)
- assert.Empty(t, user.Filters.TOTPConfig.Secret.GetKey())
- assert.Empty(t, user.Filters.TOTPConfig.Secret.GetAdditionalData())
- assert.Equal(t, secretPayload, user.Filters.TOTPConfig.Secret.GetPayload())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, user.Filters.TOTPConfig.Secret.GetStatus())
- assert.Len(t, user.Filters.TOTPConfig.Protocols, 2)
- assert.Contains(t, user.Filters.TOTPConfig.Protocols, common.ProtocolSSH)
- assert.Contains(t, user.Filters.TOTPConfig.Protocols, common.ProtocolFTP)
- req, err = http.NewRequest(http.MethodGet, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var recCodes []recoveryCode
- err = json.Unmarshal(rr.Body.Bytes(), &recCodes)
- assert.NoError(t, err)
- assert.Len(t, recCodes, 12)
- // regenerate recovery codes
- req, err = http.NewRequest(http.MethodPost, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // check that recovery codes are different
- req, err = http.NewRequest(http.MethodGet, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var newRecCodes []recoveryCode
- err = json.Unmarshal(rr.Body.Bytes(), &newRecCodes)
- assert.NoError(t, err)
- assert.Len(t, newRecCodes, 12)
- assert.NotEqual(t, recCodes, newRecCodes)
- // disable 2FA, the update user API should not work
- adminToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user.Filters.TOTPConfig.Enabled = false
- user.Filters.RecoveryCodes = nil
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Equal(t, defaultUsername, user.Username)
- assert.True(t, user.Filters.TOTPConfig.Enabled)
- assert.Len(t, user.Filters.RecoveryCodes, 12)
- // use the dedicated API
- req, err = http.NewRequest(http.MethodPut, userPath+"/"+defaultUsername+"/2fa/disable", nil)
- assert.NoError(t, err)
- setBearerForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.False(t, user.Filters.TOTPConfig.Enabled)
- assert.Len(t, user.Filters.RecoveryCodes, 0)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userPath+"/"+defaultUsername+"/2fa/disable", nil)
- assert.NoError(t, err)
- setBearerForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, user2FARecoveryCodesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, userTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestWebAPIChangeUserProfileMock(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- assert.False(t, user.Filters.AllowAPIKeyAuth)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- // invalid json
- req, err := http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer([]byte("{")))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- email := "userapi@example.com"
- description := "user API description"
- profileReq := make(map[string]any)
- profileReq["allow_api_key_auth"] = true
- profileReq["email"] = email
- profileReq["description"] = description
- profileReq["public_keys"] = []string{testPubKey, testPubKey1}
- asJSON, err := json.Marshal(profileReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- profileReq = make(map[string]any)
- req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
- assert.NoError(t, err)
- assert.Equal(t, email, profileReq["email"].(string))
- assert.Equal(t, description, profileReq["description"].(string))
- assert.True(t, profileReq["allow_api_key_auth"].(bool))
- assert.Len(t, profileReq["public_keys"].([]any), 2)
- // set an invalid email
- profileReq = make(map[string]any)
- profileReq["email"] = "notavalidemail"
- asJSON, err = json.Marshal(profileReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Validation error: email")
- // set an invalid public key
- profileReq = make(map[string]any)
- profileReq["public_keys"] = []string{"not a public key"}
- asJSON, err = json.Marshal(profileReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Validation error: could not parse key")
- user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientPubKeyChangeDisabled}
- user.Email = email
- user.Description = description
- user.Filters.AllowAPIKeyAuth = true
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- profileReq = make(map[string]any)
- profileReq["allow_api_key_auth"] = false
- profileReq["email"] = email
- profileReq["description"] = description + "_mod"
- profileReq["public_keys"] = []string{testPubKey}
- asJSON, err = json.Marshal(profileReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Profile updated")
- // check that api key auth and public keys were not changed
- profileReq = make(map[string]any)
- req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
- assert.NoError(t, err)
- assert.Equal(t, email, profileReq["email"].(string))
- assert.Equal(t, description+"_mod", profileReq["description"].(string))
- assert.True(t, profileReq["allow_api_key_auth"].(bool))
- assert.Len(t, profileReq["public_keys"].([]any), 2)
- user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled}
- user.Description = description + "_mod"
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- profileReq = make(map[string]any)
- profileReq["allow_api_key_auth"] = false
- profileReq["email"] = "newemail@apiuser.com"
- profileReq["description"] = description
- profileReq["public_keys"] = []string{testPubKey}
- asJSON, err = json.Marshal(profileReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- profileReq = make(map[string]any)
- req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
- assert.NoError(t, err)
- assert.Equal(t, email, profileReq["email"].(string))
- assert.Equal(t, description+"_mod", profileReq["description"].(string))
- assert.True(t, profileReq["allow_api_key_auth"].(bool))
- assert.Len(t, profileReq["public_keys"].([]any), 1)
- // finally disable all profile permissions
- user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled,
- sdk.WebClientPubKeyChangeDisabled}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "You are not allowed to change anything")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestPermGroupOverride(t *testing.T) {
- g := getTestGroup()
- g.UserSettings.Filters.WebClient = []string{sdk.WebClientPasswordChangeDisabled}
- group, _, err := httpdtest.AddGroup(g, http.StatusCreated)
- assert.NoError(t, err)
- u := getTestUser()
- u.Groups = []sdk.GroupMapping{
- {
- Name: group.Name,
- Type: sdk.GroupTypePrimary,
- },
- }
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- pwd := make(map[string]string)
- pwd["current_password"] = defaultPassword
- pwd["new_password"] = altAdminPassword
- asJSON, err := json.Marshal(pwd)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPut, userPwdPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- group.UserSettings.Filters.TwoFactorAuthProtocols = []string{common.ProtocolHTTP}
- group, _, err = httpdtest.UpdateGroup(group, http.StatusOK)
- assert.NoError(t, err)
- token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Two-factor authentication requirements not met, please configure two-factor authentication for the following protocols")
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- req.RequestURI = webClientFilesPath
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Two-factor authentication requirements not met, please configure two-factor authentication for the following protocols")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestWebAPIChangeUserPwdMock(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- // invalid json
- req, err := http.NewRequest(http.MethodPut, userPwdPath, bytes.NewBuffer([]byte("{")))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- pwd := make(map[string]string)
- pwd["current_password"] = defaultPassword
- pwd["new_password"] = defaultPassword
- asJSON, err := json.Marshal(pwd)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userPwdPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the new password must be different from the current one")
- pwd["new_password"] = altAdminPassword
- asJSON, err = json.Marshal(pwd)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userPwdPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, altAdminPassword)
- assert.NoError(t, err)
- assert.NotEmpty(t, token)
- // remove the change password permission
- user.Filters.WebClient = []string{sdk.WebClientPasswordChangeDisabled}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Len(t, user.Filters.WebClient, 1)
- assert.Contains(t, user.Filters.WebClient, sdk.WebClientPasswordChangeDisabled)
- token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, altAdminPassword)
- assert.NoError(t, err)
- assert.NotEmpty(t, token)
- pwd["current_password"] = altAdminPassword
- pwd["new_password"] = defaultPassword
- asJSON, err = json.Marshal(pwd)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userPwdPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestLoginInvalidPasswordMock(t *testing.T) {
- _, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass+"1")
- assert.Error(t, err)
- // now a login with no credentials
- req, _ := http.NewRequest(http.MethodGet, "/api/v2/token", nil)
- rr := executeRequest(req)
- assert.Equal(t, http.StatusUnauthorized, rr.Code)
- }
- func TestWebAPIChangeAdminProfileMock(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- assert.False(t, admin.Filters.AllowAPIKeyAuth)
- token, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- // invalid json
- req, err := http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer([]byte("{")))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- email := "adminapi@example.com"
- description := "admin API description"
- profileReq := make(map[string]any)
- profileReq["allow_api_key_auth"] = true
- profileReq["email"] = email
- profileReq["description"] = description
- asJSON, err := json.Marshal(profileReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Profile updated")
- profileReq = make(map[string]any)
- req, err = http.NewRequest(http.MethodGet, adminProfilePath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
- assert.NoError(t, err)
- assert.Equal(t, email, profileReq["email"].(string))
- assert.Equal(t, description, profileReq["description"].(string))
- assert.True(t, profileReq["allow_api_key_auth"].(bool))
- // set an invalid email
- profileReq["email"] = "admin_invalid_email"
- asJSON, err = json.Marshal(profileReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Validation error: email")
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, adminProfilePath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestChangeAdminPwdMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin.Permissions = []string{dataprovider.PermAdminAddUsers, dataprovider.PermAdminDeleteUsers}
- asJSON, err := json.Marshal(admin)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, adminPath, bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- altToken, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, _ = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- pwd := make(map[string]string)
- pwd["current_password"] = altAdminPassword
- pwd["new_password"] = defaultTokenAuthPass
- asJSON, err = json.Marshal(pwd)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, adminPwdPath, bytes.NewBuffer(asJSON))
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.Error(t, err)
- altToken, err = getJWTAPITokenFromTestServer(altAdminUsername, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, adminPwdPath, bytes.NewBuffer(asJSON))
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr) // current password does not match
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(adminPath, altAdminUsername), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestUpdateAdminMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- _, err = getJWTAPITokenFromTestServer(altAdminUsername, defaultTokenAuthPass)
- assert.Error(t, err)
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Permissions = []string{dataprovider.PermAdminManageAdmins}
- asJSON, err := json.Marshal(admin)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, adminPath, bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- _, err = getJWTAPITokenFromTestServer(altAdminUsername, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(adminPath, "abc"), bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, _ = http.NewRequest(http.MethodPut, path.Join(adminPath, altAdminUsername), bytes.NewBuffer([]byte("no json")))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- admin.Permissions = nil
- asJSON, err = json.Marshal(admin)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(adminPath, altAdminUsername), bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- admin = getTestAdmin()
- admin.Status = 0
- asJSON, err = json.Marshal(admin)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(adminPath, defaultTokenAuthUser), bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "you cannot disable yourself")
- admin.Status = 1
- admin.Permissions = []string{dataprovider.PermAdminAddUsers}
- asJSON, err = json.Marshal(admin)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(adminPath, defaultTokenAuthUser), bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "you cannot remove these permissions to yourself")
- admin.Permissions = []string{dataprovider.PermAdminAny}
- admin.Role = "missing role"
- asJSON, err = json.Marshal(admin)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(adminPath, defaultTokenAuthUser), bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "you cannot add/change your role")
- admin.Role = ""
- altToken, err := getJWTAPITokenFromTestServer(altAdminUsername, defaultTokenAuthPass)
- assert.NoError(t, err)
- admin.Password = "" // it must remain unchanged
- admin.Permissions = []string{dataprovider.PermAdminManageAdmins, dataprovider.PermAdminCloseConnections}
- asJSON, err = json.Marshal(admin)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(adminPath, altAdminUsername), bytes.NewBuffer(asJSON))
- setBearerForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = getJWTAPITokenFromTestServer(altAdminUsername, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(adminPath, altAdminUsername), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestAdminLastLoginWithAPIKey(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Filters.AllowAPIKeyAuth = true
- admin, resp, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Equal(t, int64(0), admin.LastLogin)
- apiKey := dataprovider.APIKey{
- Name: "admin API key",
- Scope: dataprovider.APIKeyScopeAdmin,
- Admin: altAdminUsername,
- LastUseAt: 123,
- }
- apiKey, resp, err = httpdtest.AddAPIKey(apiKey, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Equal(t, int64(0), apiKey.LastUseAt)
- req, err := http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, admin.Username)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.Greater(t, admin.LastLogin, int64(0))
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserLastLoginWithAPIKey(t *testing.T) {
- user := getTestUser()
- user.Filters.AllowAPIKeyAuth = true
- user, resp, err := httpdtest.AddUser(user, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- assert.Equal(t, int64(0), user.LastLogin)
- apiKey := dataprovider.APIKey{
- Name: "user API key",
- Scope: dataprovider.APIKeyScopeUser,
- User: user.Username,
- }
- apiKey, resp, err = httpdtest.AddAPIKey(apiKey, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Greater(t, user.LastLogin, int64(0))
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestAdminHandlingWithAPIKeys(t *testing.T) {
- sysAdmin, _, err := httpdtest.GetAdminByUsername(defaultTokenAuthUser, http.StatusOK)
- assert.NoError(t, err)
- sysAdmin.Filters.AllowAPIKeyAuth = true
- sysAdmin, _, err = httpdtest.UpdateAdmin(sysAdmin, http.StatusOK)
- assert.NoError(t, err)
- apiKey := dataprovider.APIKey{
- Name: "test admin API key",
- Scope: dataprovider.APIKeyScopeAdmin,
- Admin: defaultTokenAuthUser,
- }
- apiKey, resp, err := httpdtest.AddAPIKey(apiKey, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- asJSON, err := json.Marshal(admin)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, adminPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- _, err = getJWTAPITokenFromTestServer(altAdminUsername, defaultTokenAuthPass)
- assert.NoError(t, err)
- admin.Filters.AllowAPIKeyAuth = true
- asJSON, err = json.Marshal(admin)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, path.Join(adminPath, altAdminUsername), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(adminPath, altAdminUsername), nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var adminGet dataprovider.Admin
- err = json.Unmarshal(rr.Body.Bytes(), &adminGet)
- assert.NoError(t, err)
- assert.True(t, adminGet.Filters.AllowAPIKeyAuth)
- req, err = http.NewRequest(http.MethodPut, path.Join(adminPath, defaultTokenAuthUser), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "updating the admin impersonated with an API key is not allowed")
- // changing the password for the impersonated admin is not allowed
- pwd := make(map[string]string)
- pwd["current_password"] = defaultTokenAuthPass
- pwd["new_password"] = altAdminPassword
- asJSON, err = json.Marshal(pwd)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, adminPwdPath, bytes.NewBuffer(asJSON))
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "API key authentication is not allowed")
- req, err = http.NewRequest(http.MethodDelete, path.Join(adminPath, defaultTokenAuthUser), nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "you cannot delete yourself")
- req, err = http.NewRequest(http.MethodDelete, path.Join(adminPath, altAdminUsername), nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = httpdtest.RemoveAPIKey(apiKey, http.StatusOK)
- assert.NoError(t, err)
- dbAdmin, err := dataprovider.AdminExists(defaultTokenAuthUser)
- assert.NoError(t, err)
- dbAdmin.Filters.AllowAPIKeyAuth = false
- err = dataprovider.UpdateAdmin(&dbAdmin, "", "", "")
- assert.NoError(t, err)
- sysAdmin, _, err = httpdtest.GetAdminByUsername(defaultTokenAuthUser, http.StatusOK)
- assert.NoError(t, err)
- assert.False(t, sysAdmin.Filters.AllowAPIKeyAuth)
- }
- func TestUserHandlingWithAPIKey(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Filters.AllowAPIKeyAuth = true
- admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- apiKey := dataprovider.APIKey{
- Name: "test admin API key",
- Scope: dataprovider.APIKeyScopeAdmin,
- Admin: admin.Username,
- }
- apiKey, _, err = httpdtest.AddAPIKey(apiKey, http.StatusCreated)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, err := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- user.Filters.DisableFsChecks = true
- user.Description = "desc"
- userAsJSON = getUserAsJSON(t, user)
- req, err = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer(userAsJSON))
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updatedUser dataprovider.User
- err = json.Unmarshal(rr.Body.Bytes(), &updatedUser)
- assert.NoError(t, err)
- assert.True(t, updatedUser.Filters.DisableFsChecks)
- assert.Equal(t, user.Description, updatedUser.Description)
- req, err = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetAPIKeyByID(apiKey.KeyID, http.StatusNotFound)
- assert.NoError(t, err)
- }
- func TestUpdateUserMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- // permissions should not change if empty or nil
- permissions := user.Permissions
- user.Permissions = make(map[string][]string)
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPut, userPath+"/"+user.Username, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, userPath+"/"+user.Username, nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updatedUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updatedUser)
- assert.NoError(t, err)
- for dir, perms := range permissions {
- if actualPerms, ok := updatedUser.Permissions[dir]; ok {
- for _, v := range actualPerms {
- assert.True(t, util.Contains(perms, v))
- }
- } else {
- assert.Fail(t, "Permissions directories mismatch")
- }
- }
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestUpdateUserQuotaUsageMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- var user dataprovider.User
- u := getTestUser()
- usedQuotaFiles := 1
- usedQuotaSize := int64(65535)
- u.UsedQuotaFiles = usedQuotaFiles
- u.UsedQuotaSize = usedQuotaSize
- u.QuotaFiles = 100
- userAsJSON := getUserAsJSON(t, u)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "users", u.Username, "usage"), bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- assert.Equal(t, usedQuotaFiles, user.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize, user.UsedQuotaSize)
- // now update only quota size
- u.UsedQuotaFiles = 0
- userAsJSON = getUserAsJSON(t, u)
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "users", u.Username, "usage")+"?mode=add", bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- assert.Equal(t, usedQuotaFiles, user.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize*2, user.UsedQuotaSize)
- // only quota files
- u.UsedQuotaFiles = usedQuotaFiles
- u.UsedQuotaSize = 0
- userAsJSON = getUserAsJSON(t, u)
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "users", u.Username, "usage")+"?mode=add", bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- assert.Equal(t, usedQuotaFiles*2, user.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize*2, user.UsedQuotaSize)
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "users", u.Username, "usage"), bytes.NewBuffer([]byte("string")))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.True(t, common.QuotaScans.AddUserQuotaScan(user.Username, ""))
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "users", u.Username, "usage"), bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusConflict, rr)
- assert.True(t, common.QuotaScans.RemoveUserQuotaScan(user.Username))
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestUserPermissionsMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- user.Permissions = make(map[string][]string)
- user.Permissions["/somedir"] = []string{dataprovider.PermAny}
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- user.Permissions = make(map[string][]string)
- user.Permissions["/"] = []string{dataprovider.PermAny}
- user.Permissions[".."] = []string{dataprovider.PermAny}
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- user.Permissions = make(map[string][]string)
- user.Permissions["/"] = []string{dataprovider.PermAny}
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- user.Permissions["/somedir"] = []string{"invalid"}
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- delete(user.Permissions, "/somedir")
- user.Permissions["/somedir/.."] = []string{dataprovider.PermAny}
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- delete(user.Permissions, "/somedir/..")
- user.Permissions["not_abs_path"] = []string{dataprovider.PermAny}
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- delete(user.Permissions, "not_abs_path")
- user.Permissions["/somedir/../otherdir/"] = []string{dataprovider.PermListItems}
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updatedUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updatedUser)
- assert.NoError(t, err)
- if val, ok := updatedUser.Permissions["/otherdir"]; ok {
- assert.True(t, util.Contains(val, dataprovider.PermListItems))
- assert.Equal(t, 1, len(val))
- } else {
- assert.Fail(t, "expected dir not found in permissions")
- }
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestUpdateUserInvalidJsonMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer([]byte("Invalid json")))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestUpdateUserInvalidParamsMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- user.HomeDir = ""
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- userID := user.ID
- user.ID = 0
- user.CreatedAt = 0
- userAsJSON = getUserAsJSON(t, user)
- req, _ = http.NewRequest(http.MethodPut, path.Join(userPath, user.Username), bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- user.ID = userID
- req, _ = http.NewRequest(http.MethodPut, userPath+"/0", bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestGetAdminsMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- asJSON, err := json.Marshal(admin)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, adminPath, bytes.NewBuffer(asJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, _ = http.NewRequest(http.MethodGet, adminPath+"?limit=510&offset=0&order=ASC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var admins []dataprovider.Admin
- err = render.DecodeJSON(rr.Body, &admins)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(admins), 1)
- firtAdmin := admins[0].Username
- req, _ = http.NewRequest(http.MethodGet, adminPath+"?limit=510&offset=0&order=DESC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admins = nil
- err = render.DecodeJSON(rr.Body, &admins)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(admins), 1)
- assert.NotEqual(t, firtAdmin, admins[0].Username)
- req, _ = http.NewRequest(http.MethodGet, adminPath+"?limit=510&offset=1&order=ASC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admins = nil
- err = render.DecodeJSON(rr.Body, &admins)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(admins), 1)
- assert.NotEqual(t, firtAdmin, admins[0].Username)
- req, _ = http.NewRequest(http.MethodGet, adminPath+"?limit=a&offset=0&order=ASC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodGet, adminPath+"?limit=1&offset=a&order=ASC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodGet, adminPath+"?limit=1&offset=0&order=ASCa", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(adminPath, admin.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestGetUsersMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=510&offset=0&order=ASC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var users []dataprovider.User
- err = render.DecodeJSON(rr.Body, &users)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(users), 1)
- req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=a&offset=0&order=ASC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=1&offset=a&order=ASC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodGet, userPath+"?limit=1&offset=0&order=ASCa", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestDeleteUserInvalidParamsMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodDelete, userPath+"/0", nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestGetQuotaScansMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, quotaScanPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestStartQuotaScanMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- _, err = os.Stat(user.HomeDir)
- if err == nil {
- err = os.Remove(user.HomeDir)
- assert.NoError(t, err)
- }
- // simulate a duplicate quota scan
- common.QuotaScans.AddUserQuotaScan(user.Username, "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(quotasBasePath, "users", user.Username, "scan"), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusConflict, rr)
- assert.True(t, common.QuotaScans.RemoveUserQuotaScan(user.Username))
- req, _ = http.NewRequest(http.MethodPost, path.Join(quotasBasePath, "users", user.Username, "scan"), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusAccepted, rr)
- waitForUsersQuotaScan(t, token)
- _, err = os.Stat(user.HomeDir)
- if err != nil && errors.Is(err, fs.ErrNotExist) {
- err = os.MkdirAll(user.HomeDir, os.ModePerm)
- assert.NoError(t, err)
- }
- req, _ = http.NewRequest(http.MethodPost, path.Join(quotasBasePath, "users", user.Username, "scan"), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusAccepted, rr)
- waitForUsersQuotaScan(t, token)
- req, _ = http.NewRequest(http.MethodPost, path.Join(quotasBasePath, "users", user.Username, "scan"), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusAccepted, rr)
- waitForUsersQuotaScan(t, token)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestUpdateFolderQuotaUsageMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- mappedPath := filepath.Join(os.TempDir(), "vfolder")
- folderName := filepath.Base(mappedPath)
- f := vfs.BaseVirtualFolder{
- MappedPath: mappedPath,
- Name: folderName,
- }
- usedQuotaFiles := 1
- usedQuotaSize := int64(65535)
- f.UsedQuotaFiles = usedQuotaFiles
- f.UsedQuotaSize = usedQuotaSize
- var folder vfs.BaseVirtualFolder
- folderAsJSON, err := json.Marshal(f)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer(folderAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &folder)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "folders", folder.Name, "usage"), bytes.NewBuffer(folderAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var folderGet vfs.BaseVirtualFolder
- req, _ = http.NewRequest(http.MethodGet, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &folderGet)
- assert.NoError(t, err)
- assert.Equal(t, usedQuotaFiles, folderGet.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize, folderGet.UsedQuotaSize)
- // now update only quota size
- f.UsedQuotaFiles = 0
- folderAsJSON, err = json.Marshal(f)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "folders", folder.Name, "usage")+"?mode=add",
- bytes.NewBuffer(folderAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- folderGet = vfs.BaseVirtualFolder{}
- req, _ = http.NewRequest(http.MethodGet, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &folderGet)
- assert.NoError(t, err)
- assert.Equal(t, usedQuotaFiles, folderGet.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize*2, folderGet.UsedQuotaSize)
- // now update only quota files
- f.UsedQuotaSize = 0
- f.UsedQuotaFiles = 1
- folderAsJSON, err = json.Marshal(f)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "folders", folder.Name, "usage")+"?mode=add",
- bytes.NewBuffer(folderAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- folderGet = vfs.BaseVirtualFolder{}
- req, _ = http.NewRequest(http.MethodGet, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &folderGet)
- assert.NoError(t, err)
- assert.Equal(t, usedQuotaFiles*2, folderGet.UsedQuotaFiles)
- assert.Equal(t, usedQuotaSize*2, folderGet.UsedQuotaSize)
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "folders", folder.Name, "usage"),
- bytes.NewBuffer([]byte("not a json")))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.True(t, common.QuotaScans.AddVFolderQuotaScan(folderName))
- req, _ = http.NewRequest(http.MethodPut, path.Join(quotasBasePath, "folders", folder.Name, "usage"),
- bytes.NewBuffer(folderAsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusConflict, rr)
- assert.True(t, common.QuotaScans.RemoveVFolderQuotaScan(folderName))
- req, _ = http.NewRequest(http.MethodDelete, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestStartFolderQuotaScanMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- mappedPath := filepath.Join(os.TempDir(), "vfolder")
- folderName := filepath.Base(mappedPath)
- folder := vfs.BaseVirtualFolder{
- Name: folderName,
- MappedPath: mappedPath,
- }
- folderAsJSON, err := json.Marshal(folder)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer(folderAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- _, err = os.Stat(mappedPath)
- if err == nil {
- err = os.Remove(mappedPath)
- assert.NoError(t, err)
- }
- // simulate a duplicate quota scan
- common.QuotaScans.AddVFolderQuotaScan(folderName)
- req, _ = http.NewRequest(http.MethodPost, path.Join(quotasBasePath, "folders", folder.Name, "scan"), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusConflict, rr)
- assert.True(t, common.QuotaScans.RemoveVFolderQuotaScan(folderName))
- // and now a real quota scan
- _, err = os.Stat(mappedPath)
- if err != nil && errors.Is(err, fs.ErrNotExist) {
- err = os.MkdirAll(mappedPath, os.ModePerm)
- assert.NoError(t, err)
- }
- req, _ = http.NewRequest(http.MethodPost, path.Join(quotasBasePath, "folders", folder.Name, "scan"), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusAccepted, rr)
- waitForFoldersQuotaScanPath(t, token)
- // cleanup
- req, _ = http.NewRequest(http.MethodDelete, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = os.RemoveAll(folderPath)
- assert.NoError(t, err)
- err = os.RemoveAll(mappedPath)
- assert.NoError(t, err)
- }
- func TestStartQuotaScanNonExistentUserMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- req, _ := http.NewRequest(http.MethodPost, path.Join(quotasBasePath, "users", user.Username, "scan"), nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestStartQuotaScanNonExistentFolderMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- folder := vfs.BaseVirtualFolder{
- MappedPath: os.TempDir(),
- Name: "afolder",
- }
- req, _ := http.NewRequest(http.MethodPost, path.Join(quotasBasePath, "folders", folder.Name, "scan"), nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestGetFoldersMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- mappedPath := filepath.Join(os.TempDir(), "vfolder")
- folderName := filepath.Base(mappedPath)
- folder := vfs.BaseVirtualFolder{
- Name: folderName,
- MappedPath: mappedPath,
- }
- folderAsJSON, err := json.Marshal(folder)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer(folderAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &folder)
- assert.NoError(t, err)
- var folders []vfs.BaseVirtualFolder
- url, err := url.Parse(folderPath + "?limit=510&offset=0&order=DESC")
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodGet, url.String(), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &folders)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(folders), 1)
- req, _ = http.NewRequest(http.MethodGet, folderPath+"?limit=a&offset=0&order=ASC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodGet, folderPath+"?limit=1&offset=a&order=ASC", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodGet, folderPath+"?limit=1&offset=0&order=ASCa", nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestGetVersionMock(t *testing.T) {
- req, _ := http.NewRequest(http.MethodGet, versionPath, nil)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodGet, versionPath, nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, versionPath, nil)
- setBearerForReq(req, "abcde")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- }
- func TestGetConnectionsMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, activeConnectionsPath, nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestGetStatusMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestDeleteActiveConnectionMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodDelete, activeConnectionsPath+"/connectionID", nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req.Header.Set(dataprovider.NodeTokenHeader, "Bearer abc")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "the provided token cannot be authenticated")
- req, err = http.NewRequest(http.MethodDelete, activeConnectionsPath+"/connectionID?node=node1", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestNotFoundMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, "/non/existing/path", nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestMethodNotAllowedMock(t *testing.T) {
- req, _ := http.NewRequest(http.MethodPost, activeConnectionsPath, nil)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusMethodNotAllowed, rr)
- }
- func TestHealthCheck(t *testing.T) {
- req, _ := http.NewRequest(http.MethodGet, healthzPath, nil)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "ok", rr.Body.String())
- }
- func TestRobotsTxtCheck(t *testing.T) {
- req, _ := http.NewRequest(http.MethodGet, robotsTxtPath, nil)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "User-agent: *\nDisallow: /", rr.Body.String())
- }
- func TestGetWebRootMock(t *testing.T) {
- req, _ := http.NewRequest(http.MethodGet, "/", nil)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- req, _ = http.NewRequest(http.MethodGet, webBasePath, nil)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- req, _ = http.NewRequest(http.MethodGet, webBasePathAdmin, nil)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- req, _ = http.NewRequest(http.MethodGet, webBasePathClient, nil)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- }
- func TestWebNotFoundURI(t *testing.T) {
- urlString := httpBaseURL + webBasePath + "/a"
- req, err := http.NewRequest(http.MethodGet, urlString, nil)
- assert.NoError(t, err)
- resp, err := httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusNotFound, resp.StatusCode)
- req, err = http.NewRequest(http.MethodGet, urlString, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, "invalid token")
- resp, err = httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusNotFound, resp.StatusCode)
- urlString = httpBaseURL + webBasePathClient + "/a"
- req, err = http.NewRequest(http.MethodGet, urlString, nil)
- assert.NoError(t, err)
- resp, err = httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusNotFound, resp.StatusCode)
- req, err = http.NewRequest(http.MethodGet, urlString, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, "invalid client token")
- resp, err = httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusNotFound, resp.StatusCode)
- }
- func TestLogout(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, logoutPath, nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "Your token is no longer valid")
- }
- func TestDefenderAPIInvalidIDMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, path.Join(defenderHosts, "abc"), nil) // not hex id
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "invalid host id")
- }
- func TestTokenHeaderCookie(t *testing.T) {
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setJWTCookieForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "no token found")
- req, _ = http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webStatusPath, nil)
- setBearerForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- req, _ = http.NewRequest(http.MethodGet, webStatusPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestTokenAudience(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setBearerForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "Your token audience is not valid")
- req, _ = http.NewRequest(http.MethodGet, webStatusPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webStatusPath, nil)
- setJWTCookieForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- }
- func TestWebAPILoginMock(t *testing.T) {
- _, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername+"1", defaultPassword)
- assert.Error(t, err)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword+"1")
- assert.Error(t, err)
- apiToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- // a web token is not valid for API usage
- req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "Your token audience is not valid")
- req, err = http.NewRequest(http.MethodGet, userDirsPath+"/?path=%2F", nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // API token is not valid for web usage
- req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
- setJWTCookieForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebClientLoginMock(t *testing.T) {
- _, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- // a web token is not valid for API or WebAdmin usage
- req, _ := http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setBearerForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "Your token audience is not valid")
- req, _ = http.NewRequest(http.MethodGet, webStatusPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- // bearer should not work
- req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
- setBearerForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- // now try to render client pages
- req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now logout
- req, _ = http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- // get a new token and use it after removing the user
- webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- apiUserToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
- setJWTCookieForReq(req, webToken)
- req.RemoteAddr = defaultRemoteAddr
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
- req, _ = http.NewRequest(http.MethodGet, webClientDirsPath, nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
- req, _ = http.NewRequest(http.MethodGet, webClientDownloadZipPath, nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
- req, _ = http.NewRequest(http.MethodGet, userDirsPath, nil)
- setBearerForReq(req, apiUserToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
- req, _ = http.NewRequest(http.MethodGet, userFilesPath, nil)
- setBearerForReq(req, apiUserToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
- req, _ = http.NewRequest(http.MethodPost, userStreamZipPath, bytes.NewBuffer([]byte(`{}`)))
- setBearerForReq(req, apiUserToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("public_keys", testPubKey)
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- }
- func TestWebClientLoginErrorsMock(t *testing.T) {
- form := getLoginForm("", "", "")
- req, _ := http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr := executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Invalid credentials")
- form = getLoginForm(defaultUsername, defaultPassword, "")
- req, _ = http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- }
- func TestWebClientMaxConnections(t *testing.T) {
- oldValue := common.Config.MaxTotalConnections
- common.Config.MaxTotalConnections = 1
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now add a fake connection
- fs := vfs.NewOsFs("id", os.TempDir(), "")
- connection := &httpd.Connection{
- BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolHTTP, "", "", user),
- }
- err = common.Connections.Add(connection)
- assert.NoError(t, err)
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), common.ErrConnectionDenied.Error())
- common.Connections.Remove(connection.GetID())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- assert.Len(t, common.Connections.GetStats(""), 0)
- common.Config.MaxTotalConnections = oldValue
- }
- func TestTokenInvalidIPAddress(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- req.RequestURI = webClientFilesPath
- setJWTCookieForReq(req, webToken)
- req.RemoteAddr = "1.1.1.2"
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- apiToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userDirsPath+"/?path=%2F", nil)
- assert.NoError(t, err)
- req.RemoteAddr = "2.2.2.2"
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "Your token is not valid")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestDefender(t *testing.T) {
- oldConfig := config.GetCommonConfig()
- cfg := config.GetCommonConfig()
- cfg.DefenderConfig.Enabled = true
- cfg.DefenderConfig.Threshold = 3
- cfg.DefenderConfig.ScoreLimitExceeded = 2
- err := common.Initialize(cfg, 0)
- assert.NoError(t, err)
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- remoteAddr := "172.16.5.6:9876"
- webAdminToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServerWithAddr(defaultUsername, defaultPassword, remoteAddr)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- req.RequestURI = webClientFilesPath
- setJWTCookieForReq(req, webToken)
- req.RemoteAddr = remoteAddr
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- for i := 0; i < 3; i++ {
- _, err = getJWTWebClientTokenFromTestServerWithAddr(defaultUsername, "wrong pwd", remoteAddr)
- assert.Error(t, err)
- }
- _, err = getJWTWebClientTokenFromTestServerWithAddr(defaultUsername, defaultPassword, remoteAddr)
- assert.Error(t, err)
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- req.RequestURI = webClientFilesPath
- setJWTCookieForReq(req, webToken)
- req.RemoteAddr = remoteAddr
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "your IP address is banned")
- req, _ = http.NewRequest(http.MethodGet, webUsersPath, nil)
- req.RequestURI = webUsersPath
- setJWTCookieForReq(req, webAdminToken)
- req.RemoteAddr = remoteAddr
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "your IP address is banned")
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- req.Header.Set("X-Real-IP", "127.0.0.1:2345")
- setJWTCookieForReq(req, webToken)
- req.RemoteAddr = remoteAddr
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "your IP address is banned")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- err = common.Initialize(oldConfig, 0)
- assert.NoError(t, err)
- }
- func TestPostConnectHook(t *testing.T) {
- if runtime.GOOS == osWindows {
- t.Skip("this test is not available on Windows")
- }
- common.Config.PostConnectHook = postConnectPath
- u := getTestUser()
- u.Filters.AllowAPIKeyAuth = true
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- apiKey, _, err := httpdtest.AddAPIKey(dataprovider.APIKey{
- Name: "name",
- Scope: dataprovider.APIKeyScopeUser,
- User: user.Username,
- }, http.StatusCreated)
- assert.NoError(t, err)
- err = os.WriteFile(postConnectPath, getExitCodeScriptContent(0), os.ModePerm)
- assert.NoError(t, err)
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = os.WriteFile(postConnectPath, getExitCodeScriptContent(1), os.ModePerm)
- assert.NoError(t, err)
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- common.Config.PostConnectHook = ""
- }
- func TestMaxSessions(t *testing.T) {
- u := getTestUser()
- u.MaxSessions = 1
- u.Email = "user@session.com"
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- apiToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- // now add a fake connection
- fs := vfs.NewOsFs("id", os.TempDir(), "")
- connection := &httpd.Connection{
- BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolHTTP, "", "", user),
- }
- err = common.Connections.Add(connection)
- assert.NoError(t, err)
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- // try an user API call
- req, err := http.NewRequest(http.MethodGet, userDirsPath+"/?path=%2F", nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- // web client requests
- req, err = http.NewRequest(http.MethodGet, webClientDownloadZipPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodGet, webClientDirsPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath+"?path=p", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodGet, webClientEditFilePath+"?path=file", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath+"?path=file", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- // test reset password
- smtpCfg := smtp.Config{
- Host: "127.0.0.1",
- Port: 3525,
- TemplatesPath: "templates",
- }
- err = smtpCfg.Initialize(configDir)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- lastResetCode = ""
- req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("password", defaultPassword)
- form.Set("code", lastResetCode)
- req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Password reset successfully but unable to login")
- smtpCfg = smtp.Config{}
- err = smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- common.Connections.Remove(connection.GetID())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- assert.Len(t, common.Connections.GetStats(""), 0)
- }
- func TestSFTPLoopError(t *testing.T) {
- user1 := getTestUser()
- user2 := getTestUser()
- user1.Username += "1"
- user1.Email = "user1@test.com"
- user2.Username += "2"
- user1.FsConfig = vfs.Filesystem{
- Provider: sdk.SFTPFilesystemProvider,
- SFTPConfig: vfs.SFTPFsConfig{
- BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
- Endpoint: sftpServerAddr,
- Username: user2.Username,
- },
- Password: kms.NewPlainSecret(defaultPassword),
- },
- }
- user2.FsConfig.Provider = sdk.SFTPFilesystemProvider
- user2.FsConfig.SFTPConfig = vfs.SFTPFsConfig{
- BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
- Endpoint: sftpServerAddr,
- Username: user1.Username,
- },
- Password: kms.NewPlainSecret(defaultPassword),
- }
- user1, resp, err := httpdtest.AddUser(user1, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- user2, resp, err = httpdtest.AddUser(user2, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- // test reset password
- smtpCfg := smtp.Config{
- Host: "127.0.0.1",
- Port: 3525,
- TemplatesPath: "templates",
- }
- err = smtpCfg.Initialize(configDir)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user1.Username)
- lastResetCode = ""
- req, err := http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr := executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("password", defaultPassword)
- form.Set("code", lastResetCode)
- req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Password reset successfully but unable to login")
- smtpCfg = smtp.Config{}
- err = smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- _, err = httpdtest.RemoveUser(user1, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user1.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user2, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user2.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestLoginInvalidFs(t *testing.T) {
- u := getTestUser()
- u.Filters.AllowAPIKeyAuth = true
- u.FsConfig.Provider = sdk.GCSFilesystemProvider
- u.FsConfig.GCSConfig.Bucket = "test"
- u.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("invalid JSON for credentials")
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- apiKey, _, err := httpdtest.AddAPIKey(dataprovider.APIKey{
- Name: "testk",
- Scope: dataprovider.APIKeyScopeUser,
- User: u.Username,
- }, http.StatusCreated)
- assert.NoError(t, err)
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebClientChangePwd(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webChangeClientPwdPath, nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form := make(url.Values)
- form.Set("current_password", defaultPassword)
- form.Set("new_password1", defaultPassword)
- form.Set("new_password2", defaultPassword)
- // no csrf token
- req, err = http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "the new password must be different from the current one")
- form.Set("current_password", defaultPassword+"2")
- form.Set("new_password1", defaultPassword+"1")
- form.Set("new_password2", defaultPassword+"1")
- req, _ = http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "current password does not match")
- form.Set("current_password", defaultPassword)
- form.Set("new_password1", defaultPassword+"1")
- form.Set("new_password2", defaultPassword+"1")
- req, _ = http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername+"1", defaultPassword+"1")
- assert.Error(t, err)
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword+"1")
- assert.NoError(t, err)
- // remove the change password permission
- user.Filters.WebClient = []string{sdk.WebClientPasswordChangeDisabled}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.Len(t, user.Filters.WebClient, 1)
- assert.Contains(t, user.Filters.WebClient, sdk.WebClientPasswordChangeDisabled)
- webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword+"1")
- assert.NoError(t, err)
- form.Set("current_password", defaultPassword+"1")
- form.Set("new_password1", defaultPassword)
- form.Set("new_password2", defaultPassword)
- req, _ = http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestPreDownloadHook(t *testing.T) {
- if runtime.GOOS == osWindows {
- t.Skip("this test is not available on Windows")
- }
- oldExecuteOn := common.Config.Actions.ExecuteOn
- oldHook := common.Config.Actions.Hook
- common.Config.Actions.ExecuteOn = []string{common.OperationPreDownload}
- common.Config.Actions.Hook = preActionPath
- u := getTestUser()
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- err = os.WriteFile(preActionPath, getExitCodeScriptContent(0), os.ModePerm)
- assert.NoError(t, err)
- testFileName := "testfile"
- testFileContents := []byte("file contents")
- err = os.MkdirAll(filepath.Join(user.GetHomeDir()), os.ModePerm)
- assert.NoError(t, err)
- err = os.WriteFile(filepath.Join(user.GetHomeDir(), testFileName), testFileContents, os.ModePerm)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, testFileContents, rr.Body.Bytes())
- req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, testFileContents, rr.Body.Bytes())
- err = os.WriteFile(preActionPath, getExitCodeScriptContent(1), os.ModePerm)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "permission denied")
- req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "permission denied")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- common.Config.Actions.ExecuteOn = oldExecuteOn
- common.Config.Actions.Hook = oldHook
- }
- func TestPreUploadHook(t *testing.T) {
- if runtime.GOOS == osWindows {
- t.Skip("this test is not available on Windows")
- }
- oldExecuteOn := common.Config.Actions.ExecuteOn
- oldHook := common.Config.Actions.Hook
- common.Config.Actions.ExecuteOn = []string{common.OperationPreUpload}
- common.Config.Actions.Hook = preActionPath
- u := getTestUser()
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- err = os.WriteFile(preActionPath, getExitCodeScriptContent(0), os.ModePerm)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filenames", "filepre")
- assert.NoError(t, err)
- _, err = part.Write([]byte("file content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path=filepre",
- bytes.NewBuffer([]byte("single upload content")))
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = os.WriteFile(preActionPath, getExitCodeScriptContent(1), os.ModePerm)
- assert.NoError(t, err)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path=filepre",
- bytes.NewBuffer([]byte("single upload content")))
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- common.Config.Actions.ExecuteOn = oldExecuteOn
- common.Config.Actions.Hook = oldHook
- }
- func TestShareUsage(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- testFileName := "testfile.dat"
- testFileSize := int64(65536)
- testFilePath := filepath.Join(user.GetHomeDir(), testFileName)
- err = createTestFile(testFilePath, testFileSize)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Name: "test share",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- Password: defaultPassword,
- MaxTokens: 2,
- ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(1 * time.Second)),
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodGet, sharesPath+"/unknownid", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, sharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- req.SetBasicAuth(defaultUsername, "wrong password")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- time.Sleep(2 * time.Second)
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- share.ExpiresAt = 0
- jsonReq := make(map[string]any)
- jsonReq["name"] = share.Name
- jsonReq["scope"] = share.Scope
- jsonReq["paths"] = share.Paths
- jsonReq["password"] = share.Password
- jsonReq["max_tokens"] = share.MaxTokens
- jsonReq["expires_at"] = share.ExpiresAt
- asJSON, err = json.Marshal(jsonReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userSharesPath+"/"+objectID, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Invalid share scope")
- share.MaxTokens = 3
- share.Scope = dataprovider.ShareScopeWrite
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userSharesPath+"/"+objectID, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part1, err := writer.CreateFormFile("filenames", "file1.txt")
- assert.NoError(t, err)
- _, err = part1.Write([]byte("file1 content"))
- assert.NoError(t, err)
- part2, err := writer.CreateFormFile("filenames", "file2.txt")
- assert.NoError(t, err)
- _, err = part2.Write([]byte("file2 content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Unable to parse multipart form")
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- // set the proper content type
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Allowed usage exceeded")
- share.MaxTokens = 6
- share.Scope = dataprovider.ShareScopeWrite
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userSharesPath+"/"+objectID, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webClientPubSharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- share, err = dataprovider.ShareExists(objectID, user.Username)
- assert.NoError(t, err)
- assert.Equal(t, 6, share.UsedTokens)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- share.MaxTokens = 0
- err = dataprovider.UpdateShare(&share, user.Username, "", "")
- assert.NoError(t, err)
- user.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "permission denied")
- user.Permissions["/"] = []string{dataprovider.PermAny}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- body = new(bytes.Buffer)
- writer = multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filename", "file1.txt")
- assert.NoError(t, err)
- _, err = part.Write([]byte("file content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader = bytes.NewReader(body.Bytes())
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "No files uploaded!")
- user.Filters.WebClient = []string{sdk.WebClientSharesDisabled}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- user.Filters.WebClient = []string{sdk.WebClientShareNoPasswordDisabled}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- share.Password = ""
- err = dataprovider.UpdateShare(&share, user.Username, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "sharing without a password was disabled")
- user.Filters.WebClient = []string{sdk.WebClientInfoChangeDisabled}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- share.Scope = dataprovider.ShareScopeRead
- share.Paths = []string{"/missing1", "/missing2"}
- err = dataprovider.UpdateShare(&share, user.Username, "", "")
- assert.NoError(t, err)
- defer func() {
- rcv := recover()
- assert.Equal(t, http.ErrAbortHandler, rcv)
- share, err = dataprovider.ShareExists(objectID, user.Username)
- assert.NoError(t, err)
- assert.Equal(t, 6, share.UsedTokens)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }()
- req, err = http.NewRequest(http.MethodGet, sharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- executeRequest(req)
- }
- func TestShareMaxSessions(t *testing.T) {
- u := getTestUser()
- u.MaxSessions = 1
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Name: "test share max sessions read",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodGet, sharesPath+"/"+objectID+"/dirs", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // add a fake connection
- fs := vfs.NewOsFs("id", os.TempDir(), "")
- connection := &httpd.Connection{
- BaseConnection: common.NewBaseConnection(fs.ConnectionID(), common.ProtocolHTTP, "", "", user),
- }
- err = common.Connections.Add(connection)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, sharesPath+"/"+objectID+"/dirs", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID+"/dirs", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID+"/browse", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodGet, sharesPath+"/"+objectID+"/files?path=afile", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID+"/partial", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodGet, sharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- req, err = http.NewRequest(http.MethodDelete, userSharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now test a write share
- share = dataprovider.Share{
- Name: "test share max sessions write",
- Scope: dataprovider.ShareScopeWrite,
- Paths: []string{"/"},
- }
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID = rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID, "file.txt"), bytes.NewBuffer([]byte("content")))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part1, err := writer.CreateFormFile("filenames", "file1.txt")
- assert.NoError(t, err)
- _, err = part1.Write([]byte("file1 content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusTooManyRequests, rr)
- assert.Contains(t, rr.Body.String(), "too many open sessions")
- common.Connections.Remove(connection.GetID())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- assert.Len(t, common.Connections.GetStats(""), 0)
- }
- func TestShareUploadSingle(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Name: "test share",
- Scope: dataprovider.ShareScopeWrite,
- Paths: []string{"/"},
- Password: defaultPassword,
- MaxTokens: 0,
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- content := []byte("shared file content")
- modTime := time.Now().Add(-12 * time.Hour)
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID, "file.txt"), bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- req.Header.Set("X-SFTPGO-MTIME", strconv.FormatInt(util.GetTimeAsMsSinceEpoch(modTime), 10))
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- info, err := os.Stat(filepath.Join(user.GetHomeDir(), "file.txt"))
- if assert.NoError(t, err) {
- assert.InDelta(t, util.GetTimeAsMsSinceEpoch(modTime), util.GetTimeAsMsSinceEpoch(info.ModTime()), float64(1000))
- }
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "upload"), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodPost, path.Join(webClientPubSharesPath, objectID, "file.txt"), bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- info, err = os.Stat(filepath.Join(user.GetHomeDir(), "file.txt"))
- if assert.NoError(t, err) {
- assert.InDelta(t, util.GetTimeAsMsSinceEpoch(time.Now()), util.GetTimeAsMsSinceEpoch(info.ModTime()), float64(3000))
- }
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID, "dir", "file.dat"), bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID, "%2F"), bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- err = os.MkdirAll(filepath.Join(user.GetHomeDir(), "dir"), os.ModePerm)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID, "dir"), bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "operation unsupported")
- share, err = dataprovider.ShareExists(objectID, user.Username)
- assert.NoError(t, err)
- assert.Equal(t, 2, share.UsedTokens)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID, "file1.txt"), bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestShareReadWrite(t *testing.T) {
- u := getTestUser()
- u.Filters.StartDirectory = path.Join("/start", "dir")
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- testFileName := "test.txt"
- share := dataprovider.Share{
- Name: "test share rw",
- Scope: dataprovider.ShareScopeReadWrite,
- Paths: []string{user.Filters.StartDirectory},
- Password: defaultPassword,
- MaxTokens: 0,
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- content := []byte("shared rw content")
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID, testFileName), bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- assert.FileExists(t, filepath.Join(user.GetHomeDir(), user.Filters.StartDirectory, testFileName))
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path=%2F"), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path="+testFileName), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contentDisposition := rr.Header().Get("Content-Disposition")
- assert.NotEmpty(t, contentDisposition)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "partial?files="+
- url.QueryEscape(fmt.Sprintf(`["%v"]`, testFileName))), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contentDisposition = rr.Header().Get("Content-Disposition")
- assert.NotEmpty(t, contentDisposition)
- assert.Equal(t, "application/zip", rr.Header().Get("Content-Type"))
- // invalid files list
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "partial?files="+testFileName), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- assert.Contains(t, rr.Body.String(), "Unable to get files list")
- // missing directory
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "partial?path=missing"), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- assert.Contains(t, rr.Body.String(), "Unable to get files list")
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID)+"/"+url.PathEscape("../"+testFileName),
- bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Uploading outside the share is not allowed")
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID)+"/"+url.PathEscape("/../../"+testFileName),
- bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Uploading outside the share is not allowed")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestShareUncompressed(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- testFileName := "testfile.dat"
- testFileSize := int64(65536)
- testFilePath := filepath.Join(user.GetHomeDir(), testFileName)
- err = createTestFile(testFilePath, testFileSize)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Name: "test share",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- Password: defaultPassword,
- MaxTokens: 0,
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- s, err := dataprovider.ShareExists(objectID, defaultUsername)
- assert.NoError(t, err)
- assert.Equal(t, int64(0), s.ExpiresAt)
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "application/zip", rr.Header().Get("Content-Type"))
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID+"?compress=false", nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "application/zip", rr.Header().Get("Content-Type"))
- share = dataprovider.Share{
- Name: "test share1",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{testFileName},
- Password: defaultPassword,
- MaxTokens: 0,
- }
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID = rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "application/zip", rr.Header().Get("Content-Type"))
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID+"?compress=false", nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "application/octet-stream", rr.Header().Get("Content-Type"))
- share, err = dataprovider.ShareExists(objectID, user.Username)
- assert.NoError(t, err)
- assert.Equal(t, 2, share.UsedTokens)
- user.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermUpload}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID+"?compress=false", nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- share, err = dataprovider.ShareExists(objectID, user.Username)
- assert.NoError(t, err)
- assert.Equal(t, 2, share.UsedTokens)
- user.Permissions["/"] = []string{dataprovider.PermAny}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- err = os.Remove(testFilePath)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID+"?compress=false", nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestDownloadFromShareError(t *testing.T) {
- u := getTestUser()
- u.DownloadDataTransfer = 1
- u.Filters.DefaultSharesExpiration = 10
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- user.UsedDownloadDataTransfer = 1024*1024 - 32768
- _, err = httpdtest.UpdateTransferQuotaUsage(user, "add", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(1024*1024-32768), user.UsedDownloadDataTransfer)
- testFileName := "test_share_file.dat"
- testFileSize := int64(524288)
- testFilePath := filepath.Join(user.GetHomeDir(), testFileName)
- err = createTestFile(testFilePath, testFileSize)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Name: "test share root browse",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- MaxTokens: 2,
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- s, err := dataprovider.ShareExists(objectID, defaultUsername)
- assert.NoError(t, err)
- assert.Greater(t, s.ExpiresAt, int64(0))
- defer func() {
- rcv := recover()
- assert.Equal(t, http.ErrAbortHandler, rcv)
- share, err = dataprovider.ShareExists(objectID, user.Username)
- assert.NoError(t, err)
- assert.Equal(t, 0, share.UsedTokens)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }()
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path="+testFileName), nil)
- assert.NoError(t, err)
- executeRequest(req)
- }
- func TestBrowseShares(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- testFileName := "testsharefile.dat"
- testFileNameLink := "testsharefile.link"
- shareDir := "share"
- subDir := "sub"
- testFileSize := int64(65536)
- testFilePath := filepath.Join(user.GetHomeDir(), shareDir, testFileName)
- testLinkPath := filepath.Join(user.GetHomeDir(), shareDir, testFileNameLink)
- err = createTestFile(testFilePath, testFileSize)
- assert.NoError(t, err)
- err = createTestFile(filepath.Join(user.GetHomeDir(), shareDir, subDir, testFileName), testFileSize)
- assert.NoError(t, err)
- err = os.Symlink(testFilePath, testLinkPath)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Name: "test share browse",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{shareDir},
- MaxTokens: 0,
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "upload"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Invalid share scope")
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "files?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Please set the path to a valid file")
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "dirs?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents := make([]map[string]any, 0)
- err = json.Unmarshal(rr.Body.Bytes(), &contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 2)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = make([]map[string]any, 0)
- err = json.Unmarshal(rr.Body.Bytes(), &contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 2)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs?path=%2F"+subDir), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = make([]map[string]any, 0)
- err = json.Unmarshal(rr.Body.Bytes(), &contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 1)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path=%2F.."), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Invalid share path")
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "dirs?path=%2F.."), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs?path=%2F.."), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "files?path=%2F..%2F"+testFileName), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path="+testFileName), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contentDisposition := rr.Header().Get("Content-Disposition")
- assert.NotEmpty(t, contentDisposition)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "partial?path=%2F.."), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Invalid share path")
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "files?path="+testFileName), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contentDisposition = rr.Header().Get("Content-Disposition")
- assert.NotEmpty(t, contentDisposition)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path="+subDir+"%2F"+testFileName), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contentDisposition = rr.Header().Get("Content-Disposition")
- assert.NotEmpty(t, contentDisposition)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path=missing"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "file does not exist")
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "files?path=missing"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "dirs?path=missing"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs?path=missing"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path="+testFileNameLink), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "non regular files are not supported for shares")
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "files?path="+testFileNameLink), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "non regular files are not supported for shares")
- // share a symlink
- share = dataprovider.Share{
- Name: "test share browse",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{path.Join(shareDir, testFileNameLink)},
- MaxTokens: 0,
- }
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID = rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- // uncompressed download should not work
- req, err = http.NewRequest(http.MethodGet, webClientPubSharesPath+"/"+objectID+"?compress=false", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "application/zip", rr.Header().Get("Content-Type"))
- // this share is not browsable, it does not contains a directory
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "partial"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Unable to validate share")
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "files?path="+testFileName), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "dirs?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the shared object is not a directory and so it is not browsable")
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the shared object is not a directory and so it is not browsable")
- // now test a missing shareID
- objectID = "123456"
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "files?path="+testFileName), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "dirs?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "partial?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // share a missing base path
- share = dataprovider.Share{
- Name: "test share",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{path.Join(shareDir, "missingdir")},
- MaxTokens: 0,
- }
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID = rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "unable to check the share directory")
- // share multiple paths
- share = dataprovider.Share{
- Name: "test share",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{shareDir, "/anotherdir"},
- MaxTokens: 0,
- }
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID = rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "a share with multiple paths is not browsable")
- // share the root path
- share = dataprovider.Share{
- Name: "test share root",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- MaxTokens: 0,
- }
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID = rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = make([]map[string]any, 0)
- err = json.Unmarshal(rr.Body.Bytes(), &contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 1)
- // if we require two-factor auth for HTTP protocol the share should not work anymore
- user.Filters.TwoFactorAuthProtocols = []string{common.ProtocolSSH}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user.Filters.TwoFactorAuthProtocols = []string{common.ProtocolSSH, common.ProtocolHTTP}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, path.Join(sharesPath, objectID, "dirs?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "two-factor authentication requirements not met")
- user.Filters.TwoFactorAuthProtocols = []string{common.ProtocolSSH}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- // share read/write
- share.Scope = dataprovider.ShareScopeReadWrite
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID = rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "browse?path=%2F"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // on upload we should be redirected
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "upload"), nil)
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- location := rr.Header().Get("Location")
- assert.Equal(t, path.Join(webClientPubSharesPath, objectID, "browse"), location)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestUserAPIShareErrors(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Scope: 1000,
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "invalid scope")
- // invalid json
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer([]byte("{")))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- share.Scope = dataprovider.ShareScopeWrite
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "at least a shared path is required")
- share.Paths = []string{"path1", "../path1", "/path2"}
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the write share scope requires exactly one path")
- share.Paths = []string{"", ""}
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "at least a shared path is required")
- share.Paths = []string{"path1", "../path1", "/path1"}
- share.Password = redactedSecret
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "cannot save a share with a redacted password")
- share.Password = "newpass"
- share.AllowFrom = []string{"not valid"}
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "could not parse allow from entry")
- share.AllowFrom = []string{"127.0.0.1/8"}
- share.ExpiresAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-12 * time.Hour))
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "expiration must be in the future")
- share.ExpiresAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(12 * time.Hour))
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- location := rr.Header().Get("Location")
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "name is mandatory")
- // invalid json
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer([]byte("}")))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, userSharesPath+"?limit=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestUserAPIShares(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- u := getTestUser()
- u.Username = altAdminUsername
- user1, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- token1, err := getJWTAPIUserTokenFromTestServer(user1.Username, defaultPassword)
- assert.NoError(t, err)
- // the share username will be set from the claims
- share := dataprovider.Share{
- Name: "share1",
- Description: "description1",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- CreatedAt: 1,
- UpdatedAt: 2,
- LastUseAt: 3,
- ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(2 * time.Hour)),
- Password: defaultPassword,
- MaxTokens: 10,
- UsedTokens: 2,
- AllowFrom: []string{"192.168.1.0/24"},
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- location := rr.Header().Get("Location")
- assert.NotEmpty(t, location)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- assert.Equal(t, fmt.Sprintf("%v/%v", userSharesPath, objectID), location)
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var shareGet dataprovider.Share
- err = json.Unmarshal(rr.Body.Bytes(), &shareGet)
- assert.NoError(t, err)
- assert.Equal(t, objectID, shareGet.ShareID)
- assert.Equal(t, share.Name, shareGet.Name)
- assert.Equal(t, share.Description, shareGet.Description)
- assert.Equal(t, share.Scope, shareGet.Scope)
- assert.Equal(t, share.Paths, shareGet.Paths)
- assert.Equal(t, int64(0), shareGet.LastUseAt)
- assert.Greater(t, shareGet.CreatedAt, share.CreatedAt)
- assert.Greater(t, shareGet.UpdatedAt, share.UpdatedAt)
- assert.Equal(t, share.ExpiresAt, shareGet.ExpiresAt)
- assert.Equal(t, share.MaxTokens, shareGet.MaxTokens)
- assert.Equal(t, 0, shareGet.UsedTokens)
- assert.Equal(t, share.Paths, shareGet.Paths)
- assert.Equal(t, redactedSecret, shareGet.Password)
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token1)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- s, err := dataprovider.ShareExists(objectID, defaultUsername)
- assert.NoError(t, err)
- match, err := s.CheckCredentials(defaultUsername, defaultPassword)
- assert.True(t, match)
- assert.NoError(t, err)
- match, err = s.CheckCredentials(defaultUsername, defaultPassword+"mod")
- assert.False(t, match)
- assert.Error(t, err)
- match, err = s.CheckCredentials(altAdminUsername, defaultPassword)
- assert.False(t, match)
- assert.Error(t, err)
- shareGet.ExpiresAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(3 * time.Hour))
- asJSON, err = json.Marshal(shareGet)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- s, err = dataprovider.ShareExists(objectID, defaultUsername)
- assert.NoError(t, err)
- match, err = s.CheckCredentials(defaultUsername, defaultPassword)
- assert.True(t, match)
- assert.NoError(t, err)
- match, err = s.CheckCredentials(defaultUsername, defaultPassword+"mod")
- assert.False(t, match)
- assert.Error(t, err)
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var shareGetNew dataprovider.Share
- err = json.Unmarshal(rr.Body.Bytes(), &shareGetNew)
- assert.NoError(t, err)
- assert.NotEqual(t, shareGet.UpdatedAt, shareGetNew.UpdatedAt)
- shareGet.UpdatedAt = shareGetNew.UpdatedAt
- assert.Equal(t, shareGet, shareGetNew)
- req, err = http.NewRequest(http.MethodGet, userSharesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var shares []dataprovider.Share
- err = json.Unmarshal(rr.Body.Bytes(), &shares)
- assert.NoError(t, err)
- if assert.Len(t, shares, 1) {
- assert.Equal(t, shareGetNew, shares[0])
- }
- err = dataprovider.UpdateShareLastUse(&shareGetNew, 2)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- shareGetNew = dataprovider.Share{}
- err = json.Unmarshal(rr.Body.Bytes(), &shareGetNew)
- assert.NoError(t, err)
- assert.Equal(t, 2, shareGetNew.UsedTokens, "share: %v", shareGetNew)
- assert.Greater(t, shareGetNew.LastUseAt, int64(0), "share: %v", shareGetNew)
- req, err = http.NewRequest(http.MethodGet, userSharesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token1)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- shares = nil
- err = json.Unmarshal(rr.Body.Bytes(), &shares)
- assert.NoError(t, err)
- assert.Len(t, shares, 0)
- // set an empty password
- shareGet.Password = ""
- asJSON, err = json.Marshal(shareGet)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- shareGetNew = dataprovider.Share{}
- err = json.Unmarshal(rr.Body.Bytes(), &shareGetNew)
- assert.NoError(t, err)
- assert.Empty(t, shareGetNew.Password)
- req, err = http.NewRequest(http.MethodDelete, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- share.Name = ""
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- location = rr.Header().Get("Location")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- // the share should be deleted with the associated user
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodDelete, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- _, err = httpdtest.RemoveUser(user1, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user1.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestUsersAPISharesNoPasswordDisabled(t *testing.T) {
- u := getTestUser()
- u.Filters.WebClient = []string{sdk.WebClientShareNoPasswordDisabled}
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Name: "s",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- }
- asJSON, err := json.Marshal(share)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "You are not authorized to share files/folders without a password")
- share.Password = defaultPassword
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- location := rr.Header().Get("Location")
- assert.NotEmpty(t, location)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- assert.Equal(t, fmt.Sprintf("%v/%v", userSharesPath, objectID), location)
- share.Password = ""
- asJSON, err = json.Marshal(share)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "You are not authorized to share files/folders without a password")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestUserAPIKey(t *testing.T) {
- u := getTestUser()
- u.Filters.AllowAPIKeyAuth = true
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- apiKey := dataprovider.APIKey{
- Name: "testkey",
- User: user.Username + "1",
- Scope: dataprovider.APIKeyScopeUser,
- }
- _, _, err = httpdtest.AddAPIKey(apiKey, http.StatusBadRequest)
- assert.NoError(t, err)
- apiKey.User = user.Username
- apiKey, _, err = httpdtest.AddAPIKey(apiKey, http.StatusCreated)
- assert.NoError(t, err)
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filenames", "filenametest")
- assert.NoError(t, err)
- _, err = part.Write([]byte("test file content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setAPIKeyForReq(req, apiKey.Key, "")
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var dirEntries []map[string]any
- err = json.Unmarshal(rr.Body.Bytes(), &dirEntries)
- assert.NoError(t, err)
- assert.Len(t, dirEntries, 1)
- user.Status = 0
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- user.Status = 1
- user.Filters.DeniedProtocols = []string{common.ProtocolHTTP}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- user.Filters.DeniedProtocols = []string{common.ProtocolFTP}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- apiKeyNew := dataprovider.APIKey{
- Name: apiKey.Name,
- Scope: dataprovider.APIKeyScopeUser,
- }
- apiKeyNew, _, err = httpdtest.AddAPIKey(apiKeyNew, http.StatusCreated)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKeyNew.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- // now associate a user
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKeyNew.Key, user.Username)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now with a missing user
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKeyNew.Key, user.Username+"1")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- // empty user and key not associated to any user
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKeyNew.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- apiKeyNew.ExpiresAt = util.GetTimeAsMsSinceEpoch(time.Now().Add(-24 * time.Hour))
- _, _, err = httpdtest.UpdateAPIKey(apiKeyNew, http.StatusOK)
- assert.NoError(t, err)
- // expired API key
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKeyNew.Key, user.Username)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAPIKey(apiKeyNew, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestWebClientViewPDF(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webClientViewPDFPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, webClientViewPDFPath+"?path=test.pdf", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath+"?path=test.pdf", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Unable to get file")
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath+"?path=%2F", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Invalid file")
- err = os.WriteFile(filepath.Join(user.GetHomeDir(), "test.pdf"), []byte("some text data"), 0666)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath+"?path=%2Ftest.pdf", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Invalid PDF file")
- err = createTestFile(filepath.Join(user.GetHomeDir(), "test.pdf"), 1024)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath+"?path=%2Ftest.pdf", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "does not look like a PDF")
- fakePDF := []byte(`%PDF-1.6`)
- for i := 0; i < 128; i++ {
- fakePDF = append(fakePDF, []byte(fmt.Sprintf("%d", i))...)
- }
- err = os.WriteFile(filepath.Join(user.GetHomeDir(), "test.pdf"), fakePDF, 0666)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath+"?path=%2Ftest.pdf", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/",
- DeniedPatterns: []string{"*.pdf"},
- },
- }
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath+"?path=%2Ftest.pdf", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Unable to get a reader for the file")
- user.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/",
- DeniedPatterns: []string{"*.txt"},
- },
- }
- user.Filters.DeniedProtocols = []string{common.ProtocolHTTP}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath+"?path=%2Ftest.pdf", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientGetPDFPath+"?path=%2Ftest.pdf", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestWebEditFile(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- testFile1 := "testfile1.txt"
- testFile2 := "testfile2"
- file1Size := int64(65536)
- file2Size := int64(1048576 * 2)
- err = createTestFile(filepath.Join(user.GetHomeDir(), testFile1), file1Size)
- assert.NoError(t, err)
- err = createTestFile(filepath.Join(user.GetHomeDir(), testFile2), file2Size)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webClientEditFilePath+"?path="+testFile1, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientEditFilePath+"?path="+testFile2, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "exceeds the maximum allowed size")
- req, err = http.NewRequest(http.MethodGet, webClientEditFilePath+"?path=missing", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Unable to stat file")
- req, err = http.NewRequest(http.MethodGet, webClientEditFilePath+"?path=%2F", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "does not point to a file")
- user.Filters.DeniedProtocols = []string{common.ProtocolHTTP}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientEditFilePath+"?path="+testFile1, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- user.Filters.DeniedProtocols = []string{common.ProtocolFTP}
- user.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/",
- DeniedPatterns: []string{"*.txt"},
- },
- }
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientEditFilePath+"?path="+testFile1, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Unable to get a reader")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientEditFilePath+"?path="+testFile1, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestWebGetFiles(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- testFileName := "testfile"
- testDir := "testdir"
- testFileContents := []byte("file contents")
- err = os.MkdirAll(filepath.Join(user.GetHomeDir(), testDir), os.ModePerm)
- assert.NoError(t, err)
- extensions := []string{"", ".doc", ".ppt", ".xls", ".pdf", ".mkv", ".png", ".go", ".zip", ".txt"}
- for _, ext := range extensions {
- err = os.WriteFile(filepath.Join(user.GetHomeDir(), testFileName+ext), testFileContents, os.ModePerm)
- assert.NoError(t, err)
- }
- err = os.Symlink(filepath.Join(user.GetHomeDir(), testFileName+".doc"), filepath.Join(user.GetHomeDir(), testDir, testFileName+".link"))
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testDir, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientDirsPath+"?path="+testDir, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var dirContents []map[string]string
- err = json.Unmarshal(rr.Body.Bytes(), &dirContents)
- assert.NoError(t, err)
- assert.Len(t, dirContents, 1)
- req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path="+testDir, nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var dirEntries []map[string]any
- err = json.Unmarshal(rr.Body.Bytes(), &dirEntries)
- assert.NoError(t, err)
- assert.Len(t, dirEntries, 1)
- req, _ = http.NewRequest(http.MethodGet, webClientDownloadZipPath+"?path="+url.QueryEscape("/")+"&files="+
- url.QueryEscape(fmt.Sprintf(`["%v","%v","%v"]`, testFileName, testDir, testFileName+extensions[2])), nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- filesList := []string{testFileName, testDir, testFileName + extensions[2]}
- asJSON, err := json.Marshal(filesList)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPost, userStreamZipPath, bytes.NewBuffer(asJSON))
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPost, userStreamZipPath, bytes.NewBuffer([]byte(`file`)))
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientDownloadZipPath+"?path="+url.QueryEscape("/")+"&files="+
- url.QueryEscape(fmt.Sprintf(`["%v"]`, testDir)), nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientDownloadZipPath+"?path="+url.QueryEscape("/")+"&files=notalist", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- assert.Contains(t, rr.Body.String(), "Unable to get files list")
- req, _ = http.NewRequest(http.MethodGet, webClientDirsPath+"?path=/", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- dirContents = nil
- err = json.Unmarshal(rr.Body.Bytes(), &dirContents)
- assert.NoError(t, err)
- assert.Len(t, dirContents, len(extensions)+1)
- req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path=/", nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- dirEntries = nil
- err = json.Unmarshal(rr.Body.Bytes(), &dirEntries)
- assert.NoError(t, err)
- assert.Len(t, dirEntries, len(extensions)+1)
- req, _ = http.NewRequest(http.MethodGet, webClientDirsPath+"?path=/missing", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to get directory contents")
- req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path=missing", nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to get directory contents")
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, testFileContents, rr.Body.Bytes())
- req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, testFileContents, rr.Body.Bytes())
- req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path=", nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Please set the path to a valid file")
- req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testDir, nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "is a directory")
- req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path=notafile", nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to stat the requested file")
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("Range", "bytes=2-")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusPartialContent, rr)
- assert.Equal(t, testFileContents[2:], rr.Body.Bytes())
- lastModified, err := http.ParseTime(rr.Header().Get("Last-Modified"))
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
- req.Header.Set("Range", "bytes=2-")
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusPartialContent, rr)
- assert.Equal(t, testFileContents[2:], rr.Body.Bytes())
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("Range", "bytes=-2")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusPartialContent, rr)
- assert.Equal(t, testFileContents[11:], rr.Body.Bytes())
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("Range", "bytes=-2,")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusRequestedRangeNotSatisfiable, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("Range", "bytes=1a-")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusRequestedRangeNotSatisfiable, rr)
- req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
- req.Header.Set("Range", "bytes=2b-")
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusRequestedRangeNotSatisfiable, rr)
- req, _ = http.NewRequest(http.MethodHead, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("Range", "bytes=2-")
- req.Header.Set("If-Range", lastModified.UTC().Format(http.TimeFormat))
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusPartialContent, rr)
- req, _ = http.NewRequest(http.MethodHead, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("Range", "bytes=2-")
- req.Header.Set("If-Range", lastModified.UTC().Add(-120*time.Second).Format(http.TimeFormat))
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodHead, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("If-Modified-Since", lastModified.UTC().Add(-120*time.Second).Format(http.TimeFormat))
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodHead, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("If-Modified-Since", lastModified.UTC().Add(120*time.Second).Format(http.TimeFormat))
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotModified, rr)
- req, _ = http.NewRequest(http.MethodHead, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("If-Unmodified-Since", lastModified.UTC().Add(-120*time.Second).Format(http.TimeFormat))
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusPreconditionFailed, rr)
- req, _ = http.NewRequest(http.MethodHead, userFilesPath+"?path="+testFileName, nil)
- req.Header.Set("If-Unmodified-Since", lastModified.UTC().Add(-120*time.Second).Format(http.TimeFormat))
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusPreconditionFailed, rr)
- req, _ = http.NewRequest(http.MethodHead, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("If-Unmodified-Since", lastModified.UTC().Add(120*time.Second).Format(http.TimeFormat))
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user.Filters.DeniedProtocols = []string{common.ProtocolHTTP}
- _, resp, err := httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(resp))
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientDirsPath+"?path=/", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, _ = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path="+testDir, nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- filesList = []string{testDir}
- asJSON, err = json.Marshal(filesList)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPost, userStreamZipPath, bytes.NewBuffer(asJSON))
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- user.Filters.DeniedProtocols = []string{common.ProtocolFTP}
- user.Filters.DeniedLoginMethods = []string{dataprovider.LoginMethodPassword}
- _, resp, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(resp))
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientDownloadZipPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, _ = http.NewRequest(http.MethodGet, userDirsPath+"?path="+testDir, nil)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebDirsAPI(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- testDir := "testdir"
- req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var contents []map[string]any
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 0)
- // rename a missing folder
- req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target="+testDir+"new", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // delete a missing folder
- req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path="+testDir, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // create a dir
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+testDir, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- // check the dir was created
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = nil
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- if assert.Len(t, contents, 1) {
- assert.Equal(t, testDir, contents[0]["name"])
- }
- // rename a dir with the same source and target name
- req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target="+testDir, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "operation unsupported")
- req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target=%2F"+testDir+"%2F", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "operation unsupported")
- // create a dir with missing parents
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+url.QueryEscape(path.Join("/sub/dir", testDir)), nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // setting the mkdir_parents param will work
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?mkdir_parents=true&path="+url.QueryEscape(path.Join("/sub/dir", testDir)), nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- // rename the dir
- req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target="+testDir+"new", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // delete the dir
- req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path="+testDir+"new", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // the root dir cannot be created
- req, err = http.NewRequest(http.MethodPost, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- user.Permissions["/"] = []string{dataprovider.PermListItems}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- // the user has no more the permission to create the directory
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+testDir, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- // the user is deleted, any API call should fail
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+testDir, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path="+testDir+"&target="+testDir+"new", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path="+testDir+"new", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestWebUploadSingleFile(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- content := []byte("test content")
- req, err := http.NewRequest(http.MethodPost, userUploadFilePath, bytes.NewBuffer(content))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "please set a file path")
- modTime := time.Now().Add(-24 * time.Hour)
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path=file.txt", bytes.NewBuffer(content))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- req.Header.Set("X-SFTPGO-MTIME", strconv.FormatInt(util.GetTimeAsMsSinceEpoch(modTime), 10))
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- info, err := os.Stat(filepath.Join(user.GetHomeDir(), "file.txt"))
- if assert.NoError(t, err) {
- assert.InDelta(t, util.GetTimeAsMsSinceEpoch(modTime), util.GetTimeAsMsSinceEpoch(info.ModTime()), float64(1000))
- }
- // invalid modification time will be ignored
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path=file.txt", bytes.NewBuffer(content))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- req.Header.Set("X-SFTPGO-MTIME", "123abc")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- info, err = os.Stat(filepath.Join(user.GetHomeDir(), "file.txt"))
- if assert.NoError(t, err) {
- assert.InDelta(t, util.GetTimeAsMsSinceEpoch(time.Now()), util.GetTimeAsMsSinceEpoch(info.ModTime()), float64(3000))
- }
- // upload to a missing dir will fail without the mkdir_parents param
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path="+url.QueryEscape("/subdir/file.txt"), bytes.NewBuffer(content))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?mkdir_parents=true&path="+url.QueryEscape("/subdir/file.txt"), bytes.NewBuffer(content))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- metadataReq := make(map[string]int64)
- metadataReq["modification_time"] = util.GetTimeAsMsSinceEpoch(modTime)
- asJSON, err := json.Marshal(metadataReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPatch, userFilesDirsMetadataPath+"?path=file.txt", bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- info, err = os.Stat(filepath.Join(user.GetHomeDir(), "file.txt"))
- if assert.NoError(t, err) {
- assert.InDelta(t, util.GetTimeAsMsSinceEpoch(modTime), util.GetTimeAsMsSinceEpoch(info.ModTime()), float64(1000))
- }
- // missing file
- req, err = http.NewRequest(http.MethodPatch, userFilesDirsMetadataPath+"?path=file2.txt", bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to set metadata for path")
- // invalid JSON
- req, err = http.NewRequest(http.MethodPatch, userFilesDirsMetadataPath+"?path=file.txt", bytes.NewBuffer(content))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- // missing mandatory parameter
- req, err = http.NewRequest(http.MethodPatch, userFilesDirsMetadataPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "please set a modification_time and a path")
- metadataReq = make(map[string]int64)
- asJSON, err = json.Marshal(metadataReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPatch, userFilesDirsMetadataPath+"?path=file.txt", bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "please set a modification_time and a path")
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path=%2Fdir%2Ffile.txt", bytes.NewBuffer(content))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to write file")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path=file.txt", bytes.NewBuffer(content))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
- metadataReq["modification_time"] = util.GetTimeAsMsSinceEpoch(modTime)
- asJSON, err = json.Marshal(metadataReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPatch, userFilesDirsMetadataPath+"?path=file.txt", bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to retrieve your user")
- }
- func TestWebFilesAPI(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part1, err := writer.CreateFormFile("filenames", "file1.txt")
- assert.NoError(t, err)
- _, err = part1.Write([]byte("file1 content"))
- assert.NoError(t, err)
- part2, err := writer.CreateFormFile("filenames", "file2.txt")
- assert.NoError(t, err)
- _, err = part2.Write([]byte("file2 content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Unable to parse multipart form")
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(0), user.FirstUpload)
- assert.Equal(t, int64(0), user.FirstDownload)
- // set the proper content type
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Greater(t, user.FirstUpload, int64(0))
- assert.Equal(t, int64(0), user.FirstDownload)
- // check we have 2 files
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var contents []map[string]any
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 2)
- // download a file
- req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path=file1.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "file1 content", rr.Body.String())
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Greater(t, user.FirstUpload, int64(0))
- assert.Greater(t, user.FirstDownload, int64(0))
- // overwrite the existing files
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = nil
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 2)
- // now create a dir and upload to that dir
- testDir := "tdir"
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path="+testDir, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path="+testDir, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- // upload to a missing subdir will fail without the mkdir_parents param
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path="+url.QueryEscape("/sub/"+testDir), reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath+"?mkdir_parents=true&path="+url.QueryEscape("/sub/"+testDir), reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = nil
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 4)
- req, err = http.NewRequest(http.MethodGet, userDirsPath+"?path="+testDir, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = nil
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 2)
- // rename a file
- req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=file1.txt&target=%2Ftdir%2Ffile3.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // rename a missing file
- req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=file1.txt&target=%2Ftdir%2Ffile3.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // rename a file with target name equal to source name
- req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=file1.txt&target=file1.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "operation unsupported")
- // delete a file
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file2.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // delete a missing file
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file2.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // delete a directory
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=tdir", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- // make a symlink outside the home dir and then try to delete it
- extPath := filepath.Join(os.TempDir(), "file")
- err = os.WriteFile(extPath, []byte("contents"), os.ModePerm)
- assert.NoError(t, err)
- err = os.Symlink(extPath, filepath.Join(user.GetHomeDir(), "file"))
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- err = os.Remove(extPath)
- assert.NoError(t, err)
- // remove delete and overwrite permissions
- user.Permissions["/"] = []string{dataprovider.PermListItems, dataprovider.PermDownload, dataprovider.PermUpload}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=tdir", reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=%2Ftdir%2Ffile1.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- // the user is deleted, any API call should fail
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=file1.txt&target=%2Ftdir%2Ffile3.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file2.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestStartDirectory(t *testing.T) {
- u := getTestUser()
- u.Filters.StartDirectory = "/start/dir"
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- filename := "file1.txt"
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part1, err := writer.CreateFormFile("filenames", filename)
- assert.NoError(t, err)
- _, err = part1.Write([]byte("test content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- // check we have 2 files in the defined start dir
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var contents []map[string]any
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- if assert.Len(t, contents, 1) {
- assert.Equal(t, filename, contents[0]["name"].(string))
- }
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path=file2.txt",
- bytes.NewBuffer([]byte("single upload content")))
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path=testdir", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path=testdir&target=testdir1", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path=%2Ftestdirroot", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, err = http.NewRequest(http.MethodGet, userDirsPath+"?path="+url.QueryEscape(u.Filters.StartDirectory), nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = nil
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 3)
- req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path="+filename, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path=%2F"+filename, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path="+filename+"&target="+filename+"_rename", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path=testdir1", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = nil
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 2)
- req, err = http.NewRequest(http.MethodGet, webClientDirsPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = nil
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 2)
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path="+filename+"_rename", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, userDirsPath+"?path="+url.QueryEscape(u.Filters.StartDirectory), nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- contents = nil
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 1)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebFilesTransferQuotaLimits(t *testing.T) {
- u := getTestUser()
- u.UploadDataTransfer = 1
- u.DownloadDataTransfer = 1
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- testFileName := "file.data"
- testFileSize := 550000
- testFileContents := make([]byte, testFileSize)
- n, err := io.ReadFull(rand.Reader, testFileContents)
- assert.NoError(t, err)
- assert.Equal(t, testFileSize, n)
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filenames", testFileName)
- assert.NoError(t, err)
- _, err = part.Write(testFileContents)
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, testFileContents, rr.Body.Bytes())
- // error while download is active
- downloadFunc := func() {
- defer func() {
- rcv := recover()
- assert.Equal(t, http.ErrAbortHandler, rcv)
- }()
- req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- downloadFunc()
- // error before starting the download
- req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path="+testFileName, nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- // error while upload is active
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusRequestEntityTooLarge, rr)
- // error before starting the upload
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusRequestEntityTooLarge, rr)
- // now test upload/download to/from shares
- share1 := dataprovider.Share{
- Name: "share1",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- }
- asJSON, err := json.Marshal(share1)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- req, err = http.NewRequest(http.MethodGet, sharesPath+"/"+objectID, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webClientPubSharesPath, objectID, "/partial"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Denying share read due to quota limits")
- share2 := dataprovider.Share{
- Name: "share2",
- Scope: dataprovider.ShareScopeWrite,
- Paths: []string{"/"},
- }
- asJSON, err = json.Marshal(share2)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userSharesPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- objectID = rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, sharesPath+"/"+objectID, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusRequestEntityTooLarge, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebUploadErrors(t *testing.T) {
- u := getTestUser()
- u.QuotaSize = 65535
- subDir1 := "sub1"
- subDir2 := "sub2"
- u.Permissions[path.Join("/", subDir1)] = []string{dataprovider.PermListItems}
- u.Permissions[path.Join("/", subDir2)] = []string{dataprovider.PermListItems, dataprovider.PermUpload,
- dataprovider.PermDelete}
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/sub2",
- AllowedPatterns: []string{},
- DeniedPatterns: []string{"*.zip"},
- },
- }
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filenames", "file.zip")
- assert.NoError(t, err)
- _, err = part.Write([]byte("file content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- // zip file are not allowed within sub2
- req, err := http.NewRequest(http.MethodPost, userFilesPath+"?path=sub2", reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- // we have no upload permissions within sub1
- req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=sub1", reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- // we cannot create dirs in sub2
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath+"?mkdir_parents=true&path="+url.QueryEscape("/sub2/dir"), reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to check/create missing parent dir")
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?mkdir_parents=true&path="+url.QueryEscape("/sub2/dir/test"), nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Error checking parent directories")
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?mkdir_parents=true&path="+url.QueryEscape("/sub2/dir1/file.txt"), bytes.NewBuffer([]byte("")))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Error checking parent directories")
- // create a dir and try to overwrite it with a file
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path=file.zip", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "operation unsupported")
- // try to upload to a missing parent directory
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=missingdir", reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path=file.zip", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // upload will work now
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- // overwrite the file
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- vfs.SetTempPath(filepath.Join(os.TempDir(), "missingpath"))
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- if runtime.GOOS != osWindows {
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file.zip", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- vfs.SetTempPath(filepath.Clean(os.TempDir()))
- err = os.Chmod(user.GetHomeDir(), 0555)
- assert.NoError(t, err)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Error closing file")
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path=file.zip", bytes.NewBuffer(nil))
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Error closing file")
- err = os.Chmod(user.GetHomeDir(), os.ModePerm)
- assert.NoError(t, err)
- }
- vfs.SetTempPath("")
- // upload a multipart form with no files
- body = new(bytes.Buffer)
- writer = multipart.NewWriter(body)
- err = writer.Close()
- assert.NoError(t, err)
- reader = bytes.NewReader(body.Bytes())
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=sub2", reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "No files uploaded!")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebAPIVFolder(t *testing.T) {
- u := getTestUser()
- u.QuotaSize = 65535
- vdir := "/vdir"
- mappedPath := filepath.Join(os.TempDir(), "vdir")
- folderName := filepath.Base(mappedPath)
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- Name: folderName,
- MappedPath: mappedPath,
- },
- VirtualPath: vdir,
- QuotaSize: -1,
- QuotaFiles: -1,
- })
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(user.Username, defaultPassword)
- assert.NoError(t, err)
- fileContents := []byte("test contents")
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filenames", "file.txt")
- assert.NoError(t, err)
- _, err = part.Write(fileContents)
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err := http.NewRequest(http.MethodPost, userFilesPath+"?path=vdir", reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(len(fileContents)), user.UsedQuotaSize)
- folder, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(len(fileContents)), folder.UsedQuotaSize)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath+"?path=vdir", reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(len(fileContents)), user.UsedQuotaSize)
- folder, _, err = httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, int64(len(fileContents)), folder.UsedQuotaSize)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- err = os.RemoveAll(mappedPath)
- assert.NoError(t, err)
- }
- func TestWebAPIWritePermission(t *testing.T) {
- u := getTestUser()
- u.Filters.WebClient = append(u.Filters.WebClient, sdk.WebClientWriteDisabled)
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filenames", "file.txt")
- assert.NoError(t, err)
- _, err = part.Write([]byte(""))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodPatch, userFilesPath+"?path=a&target=b", nil)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=a", nil)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodGet, userFilesPath+"?path=a.txt", nil)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodPost, userDirsPath+"?path=dir", nil)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodPatch, userDirsPath+"?path=dir&target=dir1", nil)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodDelete, userDirsPath+"?path=dir", nil)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebAPICryptFs(t *testing.T) {
- u := getTestUser()
- u.QuotaSize = 65535
- u.FsConfig.Provider = sdk.CryptedFilesystemProvider
- u.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret(defaultPassword)
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filenames", "file.txt")
- assert.NoError(t, err)
- _, err = part.Write([]byte("content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebUploadSFTP(t *testing.T) {
- u := getTestUser()
- localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- u = getTestSFTPUser()
- u.QuotaFiles = 100
- u.FsConfig.SFTPConfig.BufferSize = 2
- u.HomeDir = filepath.Join(os.TempDir(), u.Username)
- sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(sftpUser.Username, defaultPassword)
- assert.NoError(t, err)
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filenames", "file.txt")
- assert.NoError(t, err)
- _, err = part.Write([]byte("test file content"))
- assert.NoError(t, err)
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- expectedQuotaSize := int64(17)
- expectedQuotaFiles := 1
- user, _, err := httpdtest.GetUserByUsername(sftpUser.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, expectedQuotaFiles, user.UsedQuotaFiles)
- assert.Equal(t, expectedQuotaSize, user.UsedQuotaSize)
- user.QuotaSize = 10
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- // we are now overquota on overwrite
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusRequestEntityTooLarge, rr)
- assert.Contains(t, rr.Body.String(), "denying write due to space limit")
- assert.Contains(t, rr.Body.String(), "Unable to write file")
- // delete the file
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user, _, err = httpdtest.GetUserByUsername(sftpUser.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 0, user.UsedQuotaFiles)
- assert.Equal(t, int64(0), user.UsedQuotaSize)
- req, err = http.NewRequest(http.MethodPost, userUploadFilePath+"?path=file.txt",
- bytes.NewBuffer([]byte("test upload single file content")))
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusRequestEntityTooLarge, rr)
- assert.Contains(t, rr.Body.String(), "denying write due to space limit")
- assert.Contains(t, rr.Body.String(), "Error saving file")
- // delete the file
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = reader.Seek(0, io.SeekStart)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusRequestEntityTooLarge, rr)
- assert.Contains(t, rr.Body.String(), "denying write due to space limit")
- assert.Contains(t, rr.Body.String(), "Error saving file")
- // delete the file
- req, err = http.NewRequest(http.MethodDelete, userFilesPath+"?path=file.txt", nil)
- assert.NoError(t, err)
- setBearerForReq(req, webAPIToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user, _, err = httpdtest.GetUserByUsername(sftpUser.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, 0, user.UsedQuotaFiles)
- assert.Equal(t, int64(0), user.UsedQuotaSize)
- _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(localUser, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(localUser.GetHomeDir())
- assert.NoError(t, err)
- err = os.RemoveAll(sftpUser.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebAPISFTPPasswordProtectedPrivateKey(t *testing.T) {
- u := getTestUser()
- u.Password = ""
- u.PublicKeys = []string{testPubKeyPwd}
- localUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- u = getTestSFTPUser()
- u.FsConfig.SFTPConfig.Password = kms.NewEmptySecret()
- u.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret(testPrivateKeyPwd)
- u.FsConfig.SFTPConfig.KeyPassphrase = kms.NewPlainSecret(privateKeyPwd)
- u.HomeDir = filepath.Join(os.TempDir(), u.Username)
- sftpUser, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(sftpUser.Username, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // update the user, the key must be preserved
- assert.Equal(t, sdkkms.SecretStatusSecretBox, sftpUser.FsConfig.SFTPConfig.KeyPassphrase.GetStatus())
- _, _, err = httpdtest.UpdateUser(sftpUser, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // using a wrong passphrase or no passphrase should fail
- sftpUser.FsConfig.SFTPConfig.KeyPassphrase = kms.NewPlainSecret("wrong")
- _, _, err = httpdtest.UpdateUser(sftpUser, http.StatusOK, "")
- assert.NoError(t, err)
- _, err = getJWTWebClientTokenFromTestServer(sftpUser.Username, defaultPassword)
- assert.Error(t, err)
- sftpUser.FsConfig.SFTPConfig.KeyPassphrase = kms.NewEmptySecret()
- _, _, err = httpdtest.UpdateUser(sftpUser, http.StatusOK, "")
- assert.NoError(t, err)
- _, err = getJWTWebClientTokenFromTestServer(sftpUser.Username, defaultPassword)
- assert.Error(t, err)
- _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(localUser, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(localUser.GetHomeDir())
- assert.NoError(t, err)
- err = os.RemoveAll(sftpUser.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebUploadMultipartFormReadError(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, userFilesPath, nil)
- assert.NoError(t, err)
- mpartForm := &multipart.Form{
- File: make(map[string][]*multipart.FileHeader),
- }
- mpartForm.File["filenames"] = append(mpartForm.File["filenames"], &multipart.FileHeader{Filename: "missing"})
- req.MultipartForm = mpartForm
- req.Header.Add("Content-Type", "multipart/form-data")
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- assert.Contains(t, rr.Body.String(), "Unable to read uploaded file")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestCompressionErrorMock(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- defer func() {
- rcv := recover()
- assert.Equal(t, http.ErrAbortHandler, rcv)
- _, err := httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }()
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, webClientDownloadZipPath+"?path="+url.QueryEscape("/")+"&files="+
- url.QueryEscape(`["missing"]`), nil)
- setJWTCookieForReq(req, webToken)
- executeRequest(req)
- }
- func TestGetFilesSFTPBackend(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- u := getTestSFTPUser()
- u.HomeDir = filepath.Clean(os.TempDir())
- u.FsConfig.SFTPConfig.BufferSize = 2
- u.Permissions["/adir"] = nil
- u.Permissions["/adir1"] = []string{dataprovider.PermListItems}
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/adir2",
- DeniedPatterns: []string{"*.txt"},
- },
- }
- sftpUserBuffered, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- u.Username += "_unbuffered"
- u.FsConfig.SFTPConfig.BufferSize = 0
- sftpUserUnbuffered, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- testFileName := "testsftpfile"
- testDir := "testsftpdir"
- testFileContents := []byte("sftp file contents")
- err = os.MkdirAll(filepath.Join(user.GetHomeDir(), testDir, "sub"), os.ModePerm)
- assert.NoError(t, err)
- err = os.MkdirAll(filepath.Join(user.GetHomeDir(), "adir1"), os.ModePerm)
- assert.NoError(t, err)
- err = os.MkdirAll(filepath.Join(user.GetHomeDir(), "adir2"), os.ModePerm)
- assert.NoError(t, err)
- err = os.WriteFile(filepath.Join(user.GetHomeDir(), testFileName), testFileContents, os.ModePerm)
- assert.NoError(t, err)
- err = os.WriteFile(filepath.Join(user.GetHomeDir(), "adir1", "afile"), testFileContents, os.ModePerm)
- assert.NoError(t, err)
- err = os.WriteFile(filepath.Join(user.GetHomeDir(), "adir2", "afile.txt"), testFileContents, os.ModePerm)
- assert.NoError(t, err)
- for _, sftpUser := range []dataprovider.User{sftpUserBuffered, sftpUserUnbuffered} {
- webToken, err := getJWTWebClientTokenFromTestServer(sftpUser.Username, defaultPassword)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, webClientFilesPath, nil)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+path.Join(testDir, "sub"), nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+path.Join(testDir, "missing"), nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "card-body text-form-error")
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path=adir/sub", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "card-body text-form-error")
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path=adir1/afile", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "card-body text-form-error")
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path=adir2/afile.txt", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "card-body text-form-error")
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, testFileContents, rr.Body.Bytes())
- req, _ = http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- req.Header.Set("Range", "bytes=2-")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusPartialContent, rr)
- assert.Equal(t, testFileContents[2:], rr.Body.Bytes())
- _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK)
- assert.NoError(t, err)
- }
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestClientUserClose(t *testing.T) {
- u := getTestUser()
- u.UploadBandwidth = 32
- u.DownloadBandwidth = 32
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- testFileName := "file.dat"
- testFileSize := int64(524288)
- testFilePath := filepath.Join(user.GetHomeDir(), testFileName)
- err = createTestFile(testFilePath, testFileSize)
- assert.NoError(t, err)
- uploadContent := make([]byte, testFileSize)
- _, err = rand.Read(uploadContent)
- assert.NoError(t, err)
- webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
- defer func() {
- rcv := recover()
- assert.Equal(t, http.ErrAbortHandler, rcv)
- }()
- req, _ := http.NewRequest(http.MethodGet, webClientFilesPath+"?path="+testFileName, nil)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }()
- wg.Add(1)
- go func() {
- defer wg.Done()
- req, _ := http.NewRequest(http.MethodGet, webClientEditFilePath+"?path="+testFileName, nil)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- assert.Contains(t, rr.Body.String(), "Unable to read the file")
- }()
- wg.Add(1)
- go func() {
- defer wg.Done()
- body := new(bytes.Buffer)
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("filenames", "upload.dat")
- assert.NoError(t, err)
- n, err := part.Write(uploadContent)
- assert.NoError(t, err)
- assert.Equal(t, testFileSize, int64(n))
- err = writer.Close()
- assert.NoError(t, err)
- reader := bytes.NewReader(body.Bytes())
- req, err := http.NewRequest(http.MethodPost, userFilesPath, reader)
- assert.NoError(t, err)
- req.Header.Add("Content-Type", writer.FormDataContentType())
- setBearerForReq(req, webAPIToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "transfer aborted")
- }()
- // wait for the transfers
- assert.Eventually(t, func() bool {
- stats := common.Connections.GetStats("")
- if len(stats) == 3 {
- if len(stats[0].Transfers) > 0 && len(stats[1].Transfers) > 0 {
- return true
- }
- }
- return false
- }, 1*time.Second, 50*time.Millisecond)
- for _, stat := range common.Connections.GetStats("") {
- // close all the active transfers
- common.Connections.Close(stat.ConnectionID, "")
- }
- wg.Wait()
- assert.Eventually(t, func() bool { return len(common.Connections.GetStats("")) == 0 },
- 1*time.Second, 100*time.Millisecond)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestWebAdminSetupMock(t *testing.T) {
- req, err := http.NewRequest(http.MethodGet, webAdminSetupPath, nil)
- assert.NoError(t, err)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- // now delete all the admins
- admins, err := dataprovider.GetAdmins(100, 0, dataprovider.OrderASC)
- assert.NoError(t, err)
- for _, admin := range admins {
- err = dataprovider.DeleteAdmin(admin.Username, "", "", "")
- assert.NoError(t, err)
- }
- // close the provider and initializes it without creating the default admin
- os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "0")
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- // now the setup page must be rendered
- req, err = http.NewRequest(http.MethodGet, webAdminSetupPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // check redirects to the setup page
- req, err = http.NewRequest(http.MethodGet, "/", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webAdminSetupPath, rr.Header().Get("Location"))
- req, err = http.NewRequest(http.MethodGet, webBasePath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webAdminSetupPath, rr.Header().Get("Location"))
- req, err = http.NewRequest(http.MethodGet, webBasePathAdmin, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webAdminSetupPath, rr.Header().Get("Location"))
- req, err = http.NewRequest(http.MethodGet, webLoginPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webAdminSetupPath, rr.Header().Get("Location"))
- req, err = http.NewRequest(http.MethodGet, webClientLoginPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webAdminSetupPath, rr.Header().Get("Location"))
- csrfToken, err := getCSRFToken(httpBaseURL + webAdminSetupPath)
- assert.NoError(t, err)
- form := make(url.Values)
- req, err = http.NewRequest(http.MethodPost, webAdminSetupPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminSetupPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Please set a username")
- form.Set("username", defaultTokenAuthUser)
- req, err = http.NewRequest(http.MethodPost, webAdminSetupPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Please set a password")
- form.Set("password", defaultTokenAuthPass)
- req, err = http.NewRequest(http.MethodPost, webAdminSetupPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Passwords mismatch")
- form.Set("confirm_password", defaultTokenAuthPass)
- // test a parse form error
- req, err = http.NewRequest(http.MethodPost, webAdminSetupPath+"?param=p%C3%AO%GH", bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // test a dataprovider error
- err = dataprovider.Close()
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webAdminSetupPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // finally initialize the provider and create the default admin
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webAdminSetupPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webUsersPath, rr.Header().Get("Location"))
- // if we resubmit the form we get a bad request, an admin already exists
- req, err = http.NewRequest(http.MethodPost, webAdminSetupPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "an admin user already exists")
- os.Setenv("SFTPGO_DATA_PROVIDER__CREATE_DEFAULT_ADMIN", "1")
- }
- func TestWhitelist(t *testing.T) {
- configCopy := common.Config
- common.Config.MaxTotalConnections = 1
- wlFile := filepath.Join(os.TempDir(), "wl.json")
- common.Config.WhiteListFile = wlFile
- wl := common.HostListFile{
- IPAddresses: []string{"172.120.1.1", "172.120.1.2"},
- CIDRNetworks: []string{"192.8.7.0/22"},
- }
- data, err := json.Marshal(wl)
- assert.NoError(t, err)
- err = os.WriteFile(wlFile, data, 0664)
- assert.NoError(t, err)
- defer os.Remove(wlFile)
- err = common.Initialize(common.Config, 0)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, webLoginPath, nil)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), common.ErrConnectionDenied.Error())
- req.RemoteAddr = "172.120.1.1"
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req.RemoteAddr = "172.120.1.3"
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), common.ErrConnectionDenied.Error())
- req.RemoteAddr = "192.8.7.1"
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- wl.IPAddresses = append(wl.IPAddresses, "172.120.1.3")
- data, err = json.Marshal(wl)
- assert.NoError(t, err)
- err = os.WriteFile(wlFile, data, 0664)
- assert.NoError(t, err)
- err = common.Reload()
- assert.NoError(t, err)
- req.RemoteAddr = "172.120.1.3"
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- common.Config = configCopy
- err = common.Initialize(common.Config, 0)
- assert.NoError(t, err)
- }
- func TestWebAdminLoginMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webStatusPath+"notfound", nil)
- req.RequestURI = webStatusPath + "notfound"
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, _ = http.NewRequest(http.MethodGet, webStatusPath, nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webLogoutPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- cookie := rr.Header().Get("Cookie")
- assert.Empty(t, cookie)
- req, _ = http.NewRequest(http.MethodGet, logoutPath, nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, serverStatusPath, nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "Your token is no longer valid")
- req, _ = http.NewRequest(http.MethodGet, webStatusPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- // now try using wrong credentials
- form := getLoginForm(defaultTokenAuthUser, "wrong pwd", csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // try from an ip not allowed
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- a.Filters.AllowList = []string{"10.0.0.0/8"}
- _, _, err = httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- rAddr := "127.1.1.1:1234"
- csrfToken, err = getCSRFTokenMock(webLoginPath, rAddr)
- assert.NoError(t, err)
- form = getLoginForm(altAdminUsername, altAdminPassword, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.RemoteAddr = rAddr
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "login from IP 127.1.1.1 not allowed")
- rAddr = "10.9.9.9:1234"
- csrfToken, err = getCSRFTokenMock(webLoginPath, rAddr)
- assert.NoError(t, err)
- form = getLoginForm(altAdminUsername, altAdminPassword, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.RemoteAddr = rAddr
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- rAddr = "127.0.1.1:4567"
- csrfToken, err = getCSRFTokenMock(webLoginPath, rAddr)
- assert.NoError(t, err)
- form = getLoginForm(altAdminUsername, altAdminPassword, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.RemoteAddr = rAddr
- req.Header.Set("X-Forwarded-For", "10.9.9.9")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "login from IP 127.0.1.1 not allowed")
- // invalid csrf token
- form = getLoginForm(altAdminUsername, altAdminPassword, "invalid csrf")
- req, _ = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- req.RemoteAddr = "10.9.9.8:1234"
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- req, _ = http.NewRequest(http.MethodGet, webLoginPath, nil)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = httpdtest.RemoveAdmin(a, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAdminNoToken(t *testing.T) {
- req, _ := http.NewRequest(http.MethodGet, webAdminProfilePath, nil)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- req, _ = http.NewRequest(http.MethodGet, webUserPath, nil)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- req, _ = http.NewRequest(http.MethodGet, userPath, nil)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- req, _ = http.NewRequest(http.MethodGet, activeConnectionsPath, nil)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- }
- func TestWebUserShare(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- token, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- userAPItoken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Name: "test share",
- Description: "test share desc",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- ExpiresAt: util.GetTimeAsMsSinceEpoch(time.Now().Add(24 * time.Hour)),
- MaxTokens: 100,
- AllowFrom: []string{"127.0.0.0/8", "172.16.0.0/16"},
- Password: defaultPassword,
- }
- form := make(url.Values)
- form.Set("name", share.Name)
- form.Set("scope", strconv.Itoa(int(share.Scope)))
- form.Set("paths", "/")
- form.Set("max_tokens", strconv.Itoa(share.MaxTokens))
- form.Set("allowed_ip", strings.Join(share.AllowFrom, ","))
- form.Set("description", share.Description)
- form.Set("password", share.Password)
- form.Set("expiration_date", "123")
- // invalid expiration date
- req, err := http.NewRequest(http.MethodPost, webClientSharePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "cannot parse")
- form.Set("expiration_date", util.GetTimeFromMsecSinceEpoch(share.ExpiresAt).UTC().Format("2006-01-02 15:04:05"))
- form.Set("scope", "")
- // invalid scope
- req, err = http.NewRequest(http.MethodPost, webClientSharePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid syntax")
- form.Set("scope", strconv.Itoa(int(share.Scope)))
- // invalid max tokens
- form.Set("max_tokens", "t")
- req, err = http.NewRequest(http.MethodPost, webClientSharePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid syntax")
- form.Set("max_tokens", strconv.Itoa(share.MaxTokens))
- // no csrf token
- req, err = http.NewRequest(http.MethodPost, webClientSharePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- form.Set("scope", "100")
- req, err = http.NewRequest(http.MethodPost, webClientSharePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Validation error: invalid scope")
- form.Set("scope", strconv.Itoa(int(share.Scope)))
- req, err = http.NewRequest(http.MethodPost, webClientSharePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, err = http.NewRequest(http.MethodGet, userSharesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, userAPItoken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var shares []dataprovider.Share
- err = json.Unmarshal(rr.Body.Bytes(), &shares)
- assert.NoError(t, err)
- if assert.Len(t, shares, 1) {
- s := shares[0]
- assert.Equal(t, share.Name, s.Name)
- assert.Equal(t, share.Description, s.Description)
- assert.Equal(t, share.Scope, s.Scope)
- assert.Equal(t, share.Paths, s.Paths)
- assert.InDelta(t, share.ExpiresAt, s.ExpiresAt, 999)
- assert.Equal(t, share.MaxTokens, s.MaxTokens)
- assert.Equal(t, share.AllowFrom, s.AllowFrom)
- assert.Equal(t, redactedSecret, s.Password)
- share.ShareID = s.ShareID
- }
- form.Set("password", redactedSecret)
- form.Set("expiration_date", "123")
- req, err = http.NewRequest(http.MethodPost, webClientSharePath+"/unknowid", bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPost, webClientSharePath+"/"+share.ShareID, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "cannot parse")
- form.Set("expiration_date", "")
- form.Set(csrfFormToken, "")
- req, err = http.NewRequest(http.MethodPost, webClientSharePath+"/"+share.ShareID, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- form.Set("allowed_ip", "1.1.1")
- req, err = http.NewRequest(http.MethodPost, webClientSharePath+"/"+share.ShareID, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Validation error: could not parse allow from entry")
- form.Set("allowed_ip", "")
- req, err = http.NewRequest(http.MethodPost, webClientSharePath+"/"+share.ShareID, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, err = http.NewRequest(http.MethodGet, userSharesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, userAPItoken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- shares = nil
- err = json.Unmarshal(rr.Body.Bytes(), &shares)
- assert.NoError(t, err)
- if assert.Len(t, shares, 1) {
- s := shares[0]
- assert.Equal(t, share.Name, s.Name)
- assert.Equal(t, share.Description, s.Description)
- assert.Equal(t, share.Scope, s.Scope)
- assert.Equal(t, share.Paths, s.Paths)
- assert.Equal(t, int64(0), s.ExpiresAt)
- assert.Equal(t, share.MaxTokens, s.MaxTokens)
- assert.Empty(t, s.AllowFrom)
- }
- // check the password
- s, err := dataprovider.ShareExists(share.ShareID, user.Username)
- assert.NoError(t, err)
- match, err := s.CheckCredentials(user.Username, defaultPassword)
- assert.NoError(t, err)
- assert.True(t, match)
- req, err = http.NewRequest(http.MethodGet, webClientSharePath+"?path=%2F&files=a", nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Invalid share list")
- req, err = http.NewRequest(http.MethodGet, webClientSharePath+"?path=%2F&files=%5B\"adir\"%5D", nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientSharePath+"/unknown", nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, webClientSharePath+"/"+share.ShareID, nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientSharesPath+"?qlimit=a", nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientSharesPath+"?qlimit=1", nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestWebUserShareNoPasswordDisabled(t *testing.T) {
- u := getTestUser()
- u.Filters.WebClient = []string{sdk.WebClientShareNoPasswordDisabled}
- u.Filters.DefaultSharesExpiration = 15
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- user.Filters.DefaultSharesExpiration = 30
- user, _, err = httpdtest.UpdateUser(u, http.StatusOK, "")
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- token, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- userAPItoken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- share := dataprovider.Share{
- Name: "s",
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- }
- form := make(url.Values)
- form.Set("name", share.Name)
- form.Set("scope", strconv.Itoa(int(share.Scope)))
- form.Set("paths", "/")
- form.Set("max_tokens", "0")
- form.Set(csrfFormToken, csrfToken)
- req, err := http.NewRequest(http.MethodPost, webClientSharePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "You are not authorized to share files/folders without a password")
- form.Set("password", defaultPassword)
- req, err = http.NewRequest(http.MethodPost, webClientSharePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, err = http.NewRequest(http.MethodGet, webClientSharePath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, userSharesPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, userAPItoken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var shares []dataprovider.Share
- err = json.Unmarshal(rr.Body.Bytes(), &shares)
- assert.NoError(t, err)
- if assert.Len(t, shares, 1) {
- s := shares[0]
- assert.Equal(t, share.Name, s.Name)
- assert.Equal(t, share.Scope, s.Scope)
- assert.Equal(t, share.Paths, s.Paths)
- share.ShareID = s.ShareID
- }
- assert.NotEmpty(t, share.ShareID)
- form.Set("password", "")
- req, err = http.NewRequest(http.MethodPost, webClientSharePath+"/"+share.ShareID, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "You are not authorized to share files/folders without a password")
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestWebUserProfile(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- assert.NoError(t, err)
- token, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- email := "user@user.com"
- description := "User"
- form := make(url.Values)
- form.Set("allow_api_key_auth", "1")
- form.Set("email", email)
- form.Set("description", description)
- form.Set("public_keys", testPubKey)
- form.Add("public_keys", testPubKey1)
- // no csrf token
- req, err := http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, user.Filters.AllowAPIKeyAuth)
- assert.Len(t, user.PublicKeys, 2)
- assert.Equal(t, email, user.Email)
- assert.Equal(t, description, user.Description)
- // set an invalid email
- form.Set("email", "not an email")
- req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Validation error: email")
- // invalid public key
- form.Set("email", email)
- form.Set("public_keys", "invalid")
- req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Validation error: could not parse key")
- // now remove permissions
- form.Set("public_keys", testPubKey)
- user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- form.Set("allow_api_key_auth", "0")
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, user.Filters.AllowAPIKeyAuth)
- assert.Len(t, user.PublicKeys, 1)
- assert.Equal(t, email, user.Email)
- assert.Equal(t, description, user.Description)
- user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientPubKeyChangeDisabled}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- form.Set("public_keys", testPubKey)
- form.Add("public_keys", testPubKey1)
- req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, user.Filters.AllowAPIKeyAuth)
- assert.Len(t, user.PublicKeys, 1)
- assert.Equal(t, email, user.Email)
- assert.Equal(t, description, user.Description)
- user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- form.Set("email", "newemail@user.com")
- form.Set("description", "new description")
- req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, user.Filters.AllowAPIKeyAuth)
- assert.Len(t, user.PublicKeys, 2)
- assert.Equal(t, email, user.Email)
- assert.Equal(t, description, user.Description)
- // finally disable all profile permissions
- user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled,
- sdk.WebClientPubKeyChangeDisabled}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- }
- func TestWebAdminProfile(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTWebTokenFromTestServer(admin.Username, altAdminPassword)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webAdminProfilePath, nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form := make(url.Values)
- form.Set("allow_api_key_auth", "1")
- form.Set("email", "admin@example.com")
- form.Set("description", "admin desc")
- // no csrf token
- req, err = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.AllowAPIKeyAuth)
- assert.Equal(t, "admin@example.com", admin.Email)
- assert.Equal(t, "admin desc", admin.Description)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.False(t, admin.Filters.AllowAPIKeyAuth)
- assert.Empty(t, admin.Email)
- assert.Empty(t, admin.Description)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- }
- func TestWebAdminPwdChange(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin.Filters.Preferences.HideUserPageSections = 16 + 32
- admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTWebTokenFromTestServer(admin.Username, altAdminPassword)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webChangeAdminPwdPath, nil)
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form := make(url.Values)
- form.Set("current_password", altAdminPassword)
- form.Set("new_password1", altAdminPassword)
- form.Set("new_password2", altAdminPassword)
- // no csrf token
- req, _ = http.NewRequest(http.MethodPost, webChangeAdminPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- req, _ = http.NewRequest(http.MethodPost, webChangeAdminPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "the new password must be different from the current one")
- form.Set("new_password1", altAdminPassword+"1")
- form.Set("new_password2", altAdminPassword+"1")
- req, _ = http.NewRequest(http.MethodPost, webChangeAdminPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAPIKeysManagement(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiKey := dataprovider.APIKey{
- Name: "test key",
- Scope: dataprovider.APIKeyScopeAdmin,
- }
- asJSON, err := json.Marshal(apiKey)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, apiKeysPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- location := rr.Header().Get("Location")
- assert.NotEmpty(t, location)
- objectID := rr.Header().Get("X-Object-ID")
- assert.NotEmpty(t, objectID)
- assert.Equal(t, fmt.Sprintf("%v/%v", apiKeysPath, objectID), location)
- apiKey.KeyID = objectID
- response := make(map[string]string)
- err = json.Unmarshal(rr.Body.Bytes(), &response)
- assert.NoError(t, err)
- key := response["key"]
- assert.NotEmpty(t, key)
- assert.True(t, strings.HasPrefix(key, apiKey.KeyID+"."))
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var keyGet dataprovider.APIKey
- err = json.Unmarshal(rr.Body.Bytes(), &keyGet)
- assert.NoError(t, err)
- assert.Empty(t, keyGet.Key)
- assert.Equal(t, apiKey.KeyID, keyGet.KeyID)
- assert.Equal(t, apiKey.Scope, keyGet.Scope)
- assert.Equal(t, apiKey.Name, keyGet.Name)
- assert.Equal(t, int64(0), keyGet.ExpiresAt)
- assert.Equal(t, int64(0), keyGet.LastUseAt)
- assert.Greater(t, keyGet.CreatedAt, int64(0))
- assert.Greater(t, keyGet.UpdatedAt, int64(0))
- assert.Empty(t, keyGet.Description)
- assert.Empty(t, keyGet.User)
- assert.Empty(t, keyGet.Admin)
- // API key is not enabled for the admin user so this request should fail
- req, err = http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, key, admin.Username)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "the admin associated with the provided api key cannot be authenticated")
- admin.Filters.AllowAPIKeyAuth = true
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, key, admin.Username)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, key, admin.Username+"1")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- req, err = http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- // now associate the key directly to the admin
- apiKey.Admin = admin.Username
- apiKey.Description = "test description"
- asJSON, err = json.Marshal(apiKey)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, apiKeysPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var keys []dataprovider.APIKey
- err = json.Unmarshal(rr.Body.Bytes(), &keys)
- assert.NoError(t, err)
- if assert.GreaterOrEqual(t, len(keys), 1) {
- found := false
- for _, k := range keys {
- if k.KeyID == apiKey.KeyID {
- found = true
- assert.Empty(t, k.Key)
- assert.Equal(t, apiKey.Scope, k.Scope)
- assert.Equal(t, apiKey.Name, k.Name)
- assert.Equal(t, int64(0), k.ExpiresAt)
- assert.Greater(t, k.LastUseAt, int64(0))
- assert.Equal(t, k.CreatedAt, keyGet.CreatedAt)
- assert.Greater(t, k.UpdatedAt, keyGet.UpdatedAt)
- assert.Equal(t, apiKey.Description, k.Description)
- assert.Empty(t, k.User)
- assert.Equal(t, admin.Username, k.Admin)
- }
- }
- assert.True(t, found)
- }
- req, err = http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // invalid API keys
- req, err = http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, key+"invalid", "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- assert.Contains(t, rr.Body.String(), "the provided api key cannot be authenticated")
- req, err = http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, "invalid", "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- // using an API key we cannot modify/get API keys
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setAPIKeyForReq(req, key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- admin.Filters.AllowList = []string{"172.16.18.0/24"}
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- req, err = http.NewRequest(http.MethodDelete, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, versionPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "the provided api key is not valid")
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAPIKeySearch(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiKey := dataprovider.APIKey{
- Scope: dataprovider.APIKeyScopeAdmin,
- }
- for i := 1; i < 5; i++ {
- apiKey.Name = fmt.Sprintf("testapikey%v", i)
- asJSON, err := json.Marshal(apiKey)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, apiKeysPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- }
- req, err := http.NewRequest(http.MethodGet, apiKeysPath+"?limit=1&order=ASC", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var keys []dataprovider.APIKey
- err = json.Unmarshal(rr.Body.Bytes(), &keys)
- assert.NoError(t, err)
- assert.Len(t, keys, 1)
- firstKey := keys[0]
- req, err = http.NewRequest(http.MethodGet, apiKeysPath+"?limit=1&order=DESC", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- keys = nil
- err = json.Unmarshal(rr.Body.Bytes(), &keys)
- assert.NoError(t, err)
- if assert.Len(t, keys, 1) {
- assert.NotEqual(t, firstKey.KeyID, keys[0].KeyID)
- }
- req, err = http.NewRequest(http.MethodGet, apiKeysPath+"?limit=1&offset=100", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- keys = nil
- err = json.Unmarshal(rr.Body.Bytes(), &keys)
- assert.NoError(t, err)
- assert.Len(t, keys, 0)
- req, err = http.NewRequest(http.MethodGet, apiKeysPath+"?limit=a", nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v/%v", apiKeysPath, "missingid"), nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, apiKeysPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- keys = nil
- err = json.Unmarshal(rr.Body.Bytes(), &keys)
- assert.NoError(t, err)
- counter := 0
- for _, k := range keys {
- if strings.HasPrefix(k.Name, "testapikey") {
- req, err = http.NewRequest(http.MethodDelete, fmt.Sprintf("%v/%v", apiKeysPath, k.KeyID), nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- counter++
- }
- }
- assert.Equal(t, 4, counter)
- }
- func TestAPIKeyErrors(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiKey := dataprovider.APIKey{
- Name: "testkey",
- Scope: dataprovider.APIKeyScopeUser,
- }
- asJSON, err := json.Marshal(apiKey)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, apiKeysPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- location := rr.Header().Get("Location")
- assert.NotEmpty(t, location)
- // invalid API scope
- apiKey.Scope = 1000
- asJSON, err = json.Marshal(apiKey)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, apiKeysPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- // invalid JSON
- req, err = http.NewRequest(http.MethodPost, apiKeysPath, bytes.NewBuffer([]byte(`invalid JSON`)))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer([]byte(`invalid JSON`)))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- req, err = http.NewRequest(http.MethodDelete, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodDelete, location, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodPut, location, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestAPIKeyOnDeleteCascade(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin, _, err = httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- apiKey := dataprovider.APIKey{
- Name: "user api key",
- Scope: dataprovider.APIKeyScopeUser,
- User: user.Username,
- }
- apiKey, _, err = httpdtest.AddAPIKey(apiKey, http.StatusCreated)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusUnauthorized, rr)
- user.Filters.AllowAPIKeyAuth = true
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userDirsPath, nil)
- assert.NoError(t, err)
- setAPIKeyForReq(req, apiKey.Key, "")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var contents []map[string]any
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 0)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, _, err = httpdtest.GetAPIKeyByID(apiKey.KeyID, http.StatusNotFound)
- assert.NoError(t, err)
- apiKey.User = ""
- apiKey.Admin = admin.Username
- apiKey.Scope = dataprovider.APIKeyScopeAdmin
- apiKey, _, err = httpdtest.AddAPIKey(apiKey, http.StatusCreated)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetAPIKeyByID(apiKey.KeyID, http.StatusNotFound)
- assert.NoError(t, err)
- }
- func TestBasicWebUsersMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- user1 := getTestUser()
- user1.Username += "1"
- user1AsJSON := getUserAsJSON(t, user1)
- req, _ = http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(user1AsJSON))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user1)
- assert.NoError(t, err)
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodGet, webUsersPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webUsersPath+"?qlimit=a", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webUsersPath+"?qlimit=1", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webUserPath, nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(webUserPath, user.Username), nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webUserPath+"/0", nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("username", user.Username)
- form.Set(csrfFormToken, csrfToken)
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath+"/0", &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, _ = http.NewRequest(http.MethodPost, webUserPath+"/aaa", &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webUserPath, user.Username), nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Invalid token")
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webUserPath, user.Username), nil)
- setJWTCookieForReq(req, webToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webUserPath, user1.Username), nil)
- setJWTCookieForReq(req, webToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestRenderDefenderPageMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webDefenderPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "View and manage blocklist")
- }
- func TestWebAdminBasicMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("username", admin.Username)
- form.Set("password", "")
- form.Set("status", "1")
- form.Set("permissions", "*")
- form.Set("description", admin.Description)
- form.Set("user_page_hidden_sections", "1")
- form.Add("user_page_hidden_sections", "2")
- form.Add("user_page_hidden_sections", "3")
- form.Add("user_page_hidden_sections", "4")
- form.Add("user_page_hidden_sections", "5")
- form.Add("user_page_hidden_sections", "6")
- form.Add("user_page_hidden_sections", "7")
- form.Set("default_users_expiration", "10")
- req, _ := http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- form.Set("status", "a")
- req, _ = http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("status", "1")
- form.Set("default_users_expiration", "a")
- req, _ = http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid default users expiration")
- form.Set("default_users_expiration", "10")
- req, _ = http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("password", admin.Password)
- req, _ = http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // add TOTP config
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], altAdminUsername)
- assert.NoError(t, err)
- altToken, err := getJWTWebTokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- adminTOTPConfig := dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- }
- asJSON, err := json.Marshal(adminTOTPConfig)
- assert.NoError(t, err)
- // no CSRF token
- req, err = http.NewRequest(http.MethodPost, webAdminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Invalid token")
- req, err = http.NewRequest(http.MethodPost, webAdminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setJWTCookieForReq(req, altToken)
- setCSRFHeaderForReq(req, csrfToken)
- req.RemoteAddr = defaultRemoteAddr
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- secretPayload := admin.Filters.TOTPConfig.Secret.GetPayload()
- assert.NotEmpty(t, secretPayload)
- assert.Equal(t, 1+2+4+8+16+32+64, admin.Filters.Preferences.HideUserPageSections)
- assert.Equal(t, 10, admin.Filters.Preferences.DefaultUsersExpiration)
- adminTOTPConfig = dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewEmptySecret(),
- }
- asJSON, err = json.Marshal(adminTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webAdminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, altToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- assert.Equal(t, secretPayload, admin.Filters.TOTPConfig.Secret.GetPayload())
- adminTOTPConfig = dataprovider.AdminTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: nil,
- }
- asJSON, err = json.Marshal(adminTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webAdminTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, altToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webAdminsPath+"?qlimit=a", nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webAdminsPath+"?qlimit=1", nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webAdminPath, nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("password", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, altAdminUsername), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- form.Set(csrfFormToken, "invalid csrf")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, altAdminUsername), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- form.Set("email", "not-an-email")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, altAdminUsername), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("email", "")
- form.Set("status", "b")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, altAdminUsername), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("email", "admin@example.com")
- form.Set("status", "0")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, altAdminUsername), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, admin.Filters.TOTPConfig.Enabled)
- assert.Equal(t, "admin@example.com", admin.Email)
- assert.Equal(t, 0, admin.Status)
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, altAdminUsername+"1"), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(webAdminPath, altAdminUsername), nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(webAdminPath, altAdminUsername+"1"), nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, _ = http.NewRequest(http.MethodGet, webUserPath, nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webAdminPath, altAdminUsername), nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, webUserPath, nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- assert.Contains(t, rr.Body.String(), "unable to get the admin")
- _, err = httpdtest.RemoveAdmin(admin, http.StatusNotFound)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webAdminPath, defaultTokenAuthUser), nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "you cannot delete yourself")
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webAdminPath, defaultTokenAuthUser), nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Invalid token")
- }
- func TestWebAdminGroupsMock(t *testing.T) {
- group1 := getTestGroup()
- group1.Name += "_1"
- group1, _, err := httpdtest.AddGroup(group1, http.StatusCreated)
- assert.NoError(t, err)
- group2 := getTestGroup()
- group2.Name += "_2"
- group2, _, err = httpdtest.AddGroup(group2, http.StatusCreated)
- assert.NoError(t, err)
- group3 := getTestGroup()
- group3.Name += "_3"
- group3, _, err = httpdtest.AddGroup(group3, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", admin.Username)
- form.Set("password", "")
- form.Set("status", "1")
- form.Set("permissions", "*")
- form.Set("description", admin.Description)
- form.Set("password", admin.Password)
- form.Set("group1", group1.Name)
- form.Set("add_as_group_type1", "1")
- form.Set("group2", group2.Name)
- form.Set("add_as_group_type2", "2")
- form.Set("group3", group3.Name)
- req, err := http.NewRequest(http.MethodPost, webAdminPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- if assert.Len(t, admin.Groups, 3) {
- for _, g := range admin.Groups {
- switch g.Name {
- case group1.Name:
- assert.Equal(t, dataprovider.GroupAddToUsersAsPrimary, g.Options.AddToUsersAs)
- case group2.Name:
- assert.Equal(t, dataprovider.GroupAddToUsersAsSecondary, g.Options.AddToUsersAs)
- case group3.Name:
- assert.Equal(t, dataprovider.GroupAddToUsersAsMembership, g.Options.AddToUsersAs)
- default:
- t.Errorf("unexpected group %q", g.Name)
- }
- }
- }
- adminToken, err := getJWTWebTokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webUserPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webUserPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, adminToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- _, err = httpdtest.RemoveGroup(group1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group2, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group3, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestWebAdminPermissions(t *testing.T) {
- admin := getTestAdmin()
- admin.Username = altAdminUsername
- admin.Password = altAdminPassword
- admin.Permissions = []string{dataprovider.PermAdminAddUsers}
- _, _, err := httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- token, err := getJWTWebToken(altAdminUsername, altAdminPassword)
- require.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, httpBaseURL+webUserPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- resp, err := httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- req, err = http.NewRequest(http.MethodGet, httpBaseURL+path.Join(webUserPath, "auser"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- resp, err = httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusForbidden, resp.StatusCode)
- req, err = http.NewRequest(http.MethodGet, httpBaseURL+webFolderPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- resp, err = httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- req, err = http.NewRequest(http.MethodGet, httpBaseURL+webStatusPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- resp, err = httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusForbidden, resp.StatusCode)
- req, err = http.NewRequest(http.MethodGet, httpBaseURL+webConnectionsPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- resp, err = httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusForbidden, resp.StatusCode)
- req, err = http.NewRequest(http.MethodGet, httpBaseURL+webAdminPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- resp, err = httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusForbidden, resp.StatusCode)
- req, err = http.NewRequest(http.MethodGet, httpBaseURL+path.Join(webAdminPath, "a"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- resp, err = httpclient.GetHTTPClient().Do(req)
- require.NoError(t, err)
- defer resp.Body.Close()
- assert.Equal(t, http.StatusForbidden, resp.StatusCode)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAdminUpdateSelfMock(t *testing.T) {
- admin, _, err := httpdtest.GetAdminByUsername(defaultTokenAuthUser, http.StatusOK)
- assert.NoError(t, err)
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("username", admin.Username)
- form.Set("password", admin.Password)
- form.Set("status", "0")
- form.Set("permissions", dataprovider.PermAdminAddUsers)
- form.Set("permissions", dataprovider.PermAdminCloseConnections)
- form.Set(csrfFormToken, csrfToken)
- req, _ := http.NewRequest(http.MethodPost, path.Join(webAdminPath, defaultTokenAuthUser), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "You cannot remove these permissions to yourself")
- form.Set("permissions", dataprovider.PermAdminAny)
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, defaultTokenAuthUser), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "You cannot disable yourself")
- form.Set("status", "1")
- form.Set("role", "my role")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, defaultTokenAuthUser), bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "You cannot add/change your role")
- }
- func TestWebMaintenanceMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, webMaintenancePath, nil)
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("mode", "a")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webRestorePath, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webRestorePath, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("mode", "0")
- form.Set("quota", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webRestorePath, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("quota", "0")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webRestorePath, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodPost, webRestorePath+"?a=%3", &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- backupFilePath := filepath.Join(os.TempDir(), "backup.json")
- err = createTestFile(backupFilePath, 0)
- assert.NoError(t, err)
- b, contentType, _ = getMultipartFormData(form, "backup_file", backupFilePath)
- req, _ = http.NewRequest(http.MethodPost, webRestorePath, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = createTestFile(backupFilePath, 10)
- assert.NoError(t, err)
- b, contentType, _ = getMultipartFormData(form, "backup_file", backupFilePath)
- req, _ = http.NewRequest(http.MethodPost, webRestorePath, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user := getTestUser()
- user.ID = 1
- user.Username = "test_user_web_restore"
- admin := getTestAdmin()
- admin.ID = 1
- admin.Username = "test_admin_web_restore"
- apiKey := dataprovider.APIKey{
- Name: "key name",
- KeyID: util.GenerateUniqueID(),
- Key: fmt.Sprintf("%v.%v", util.GenerateUniqueID(), util.GenerateUniqueID()),
- Scope: dataprovider.APIKeyScopeAdmin,
- }
- backupData := dataprovider.BackupData{}
- backupData.Users = append(backupData.Users, user)
- backupData.Admins = append(backupData.Admins, admin)
- backupData.APIKeys = append(backupData.APIKeys, apiKey)
- backupContent, err := json.Marshal(backupData)
- assert.NoError(t, err)
- err = os.WriteFile(backupFilePath, backupContent, os.ModePerm)
- assert.NoError(t, err)
- b, contentType, _ = getMultipartFormData(form, "backup_file", backupFilePath)
- req, _ = http.NewRequest(http.MethodPost, webRestorePath, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Your backup was successfully restored")
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetAPIKeyByID(apiKey.KeyID, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAPIKey(apiKey, http.StatusOK)
- assert.NoError(t, err)
- err = os.Remove(backupFilePath)
- assert.NoError(t, err)
- }
- func TestWebUserAddMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- group1 := getTestGroup()
- group1.Name += "_1"
- group1, _, err = httpdtest.AddGroup(group1, http.StatusCreated)
- assert.NoError(t, err)
- group2 := getTestGroup()
- group2.Name += "_2"
- group2, _, err = httpdtest.AddGroup(group2, http.StatusCreated)
- assert.NoError(t, err)
- group3 := getTestGroup()
- group3.Name += "_3"
- group3, _, err = httpdtest.AddGroup(group3, http.StatusCreated)
- assert.NoError(t, err)
- user := getTestUser()
- user.UploadBandwidth = 32
- user.DownloadBandwidth = 64
- user.UploadDataTransfer = 1000
- user.DownloadDataTransfer = 2000
- user.UID = 1000
- user.AdditionalInfo = "info"
- user.Description = "user dsc"
- user.Email = "test@test.com"
- mappedDir := filepath.Join(os.TempDir(), "mapped")
- folderName := filepath.Base(mappedDir)
- f := vfs.BaseVirtualFolder{
- Name: folderName,
- MappedPath: mappedDir,
- }
- folderAsJSON, err := json.Marshal(f)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer(folderAsJSON))
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- form.Set("email", user.Email)
- form.Set("home_dir", user.HomeDir)
- form.Set("password", user.Password)
- form.Set("primary_group", group1.Name)
- form.Set("secondary_groups", group2.Name)
- form.Set("membership_groups", group3.Name)
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "")
- form.Set("permissions", "*")
- form.Set("sub_perm_path0", "/subdir")
- form.Set("sub_perm_permissions0", "list")
- form.Add("sub_perm_permissions0", "download")
- form.Set("vfolder_path", " /vdir")
- form.Set("vfolder_name", folderName)
- form.Set("vfolder_quota_size", "1024")
- form.Set("vfolder_quota_files", "2")
- form.Set("pattern_path0", "/dir2")
- form.Set("patterns0", "*.jpg,*.png")
- form.Set("pattern_type0", "allowed")
- form.Set("pattern_policy0", "1")
- form.Set("pattern_path1", "/dir1")
- form.Set("patterns1", "*.png")
- form.Set("pattern_type1", "allowed")
- form.Set("pattern_path2", "/dir1")
- form.Set("patterns2", "*.zip")
- form.Set("pattern_type2", "denied")
- form.Set("pattern_path3", "/dir3")
- form.Set("patterns3", "*.rar")
- form.Set("pattern_type3", "denied")
- form.Set("pattern_path4", "/dir2")
- form.Set("patterns4", "*.mkv")
- form.Set("pattern_type4", "denied")
- form.Set("additional_info", user.AdditionalInfo)
- form.Set("description", user.Description)
- form.Add("hooks", "external_auth_disabled")
- form.Set("disable_fs_checks", "checked")
- form.Set("total_data_transfer", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("start_directory", "start/dir")
- form.Set("require_password_change", "1")
- b, contentType, _ := getMultipartFormData(form, "", "")
- // test invalid url escape
- req, _ = http.NewRequest(http.MethodPost, webUserPath+"?a=%2", &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("public_keys", testPubKey)
- form.Add("public_keys", testPubKey1)
- form.Set("uid", strconv.FormatInt(int64(user.UID), 10))
- form.Set("gid", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid gid
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("gid", "0")
- form.Set("max_sessions", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid max sessions
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("max_sessions", "0")
- form.Set("quota_size", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid quota size
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("quota_size", "0")
- form.Set("quota_files", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid quota files
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("quota_files", "0")
- form.Set("upload_bandwidth", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid upload bandwidth
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("upload_bandwidth", strconv.FormatInt(user.UploadBandwidth, 10))
- form.Set("download_bandwidth", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid download bandwidth
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("download_bandwidth", strconv.FormatInt(user.DownloadBandwidth, 10))
- form.Set("upload_data_transfer", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid upload data transfer
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("upload_data_transfer", strconv.FormatInt(user.UploadDataTransfer, 10))
- form.Set("download_data_transfer", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid download data transfer
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("download_data_transfer", strconv.FormatInt(user.DownloadDataTransfer, 10))
- form.Set("total_data_transfer", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid total data transfer
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("total_data_transfer", strconv.FormatInt(user.TotalDataTransfer, 10))
- form.Set("status", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid status
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "123")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid expiration date
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("expiration_date", "")
- form.Set("allowed_ip", "invalid,ip")
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid allowed_ip
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("allowed_ip", "")
- form.Set("denied_ip", "192.168.1.2") // it should be 192.168.1.2/32
- b, contentType, _ = getMultipartFormData(form, "", "")
- // test invalid denied_ip
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("denied_ip", "")
- // test invalid max file upload size
- form.Set("max_upload_file_size", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("max_upload_file_size", "1 KB")
- // test invalid default shares expiration
- form.Set("default_shares_expiration", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid default shares expiration")
- form.Set("default_shares_expiration", "10")
- // test invalid password expiration
- form.Set("password_expiration", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid password expiration")
- form.Set("password_expiration", "90")
- // test invalid tls username
- form.Set("tls_username", "username")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Validation error: invalid TLS username")
- form.Set("tls_username", string(sdk.TLSUsernameNone))
- // invalid upload_bandwidth_source0
- form.Set("bandwidth_limit_sources0", "192.168.1.0/24, 192.168.2.0/25")
- form.Set("upload_bandwidth_source0", "a")
- form.Set("download_bandwidth_source0", "0")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid upload_bandwidth_source")
- // invalid download_bandwidth_source0
- form.Set("upload_bandwidth_source0", "256")
- form.Set("download_bandwidth_source0", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid download_bandwidth_source")
- form.Set("download_bandwidth_source0", "512")
- form.Set("download_bandwidth_source1", "1024")
- form.Set("bandwidth_limit_sources1", "1.1.1")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "Validation error: could not parse bandwidth limit source")
- form.Set("bandwidth_limit_sources1", "127.0.0.1/32")
- form.Set("upload_bandwidth_source1", "-1")
- form.Set("data_transfer_limit_sources0", "127.0.1.1")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "could not parse data transfer limit source")
- form.Set("data_transfer_limit_sources0", "127.0.1.1/32")
- form.Set("upload_data_transfer_source0", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid upload_data_transfer_source")
- form.Set("upload_data_transfer_source0", "0")
- form.Set("download_data_transfer_source0", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid download_data_transfer_source")
- form.Set("download_data_transfer_source0", "0")
- form.Set("total_data_transfer_source0", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid total_data_transfer_source")
- form.Set("total_data_transfer_source0", "0")
- form.Set("data_transfer_limit_sources10", "192.168.5.0/24, 10.8.0.0/16")
- form.Set("download_data_transfer_source10", "100")
- form.Set("upload_data_transfer_source10", "120")
- form.Set("data_transfer_limit_sources12", "192.168.3.0/24, 10.8.2.0/24,::1/64")
- form.Set("download_data_transfer_source12", "100")
- form.Set("upload_data_transfer_source12", "120")
- form.Set("total_data_transfer_source12", "200")
- // invalid external auth cache size
- form.Set("external_auth_cache_time", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("external_auth_cache_time", "0")
- form.Set(csrfFormToken, "invalid form token")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- dbUser, err := dataprovider.UserExists(user.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, dbUser.Password)
- assert.True(t, dbUser.IsPasswordHashed())
- // the user already exists, was created with the above request
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- newUser := dataprovider.User{}
- err = render.DecodeJSON(rr.Body, &newUser)
- assert.NoError(t, err)
- assert.Equal(t, user.UID, newUser.UID)
- assert.Equal(t, user.UploadBandwidth, newUser.UploadBandwidth)
- assert.Equal(t, user.DownloadBandwidth, newUser.DownloadBandwidth)
- assert.Equal(t, user.UploadDataTransfer, newUser.UploadDataTransfer)
- assert.Equal(t, user.DownloadDataTransfer, newUser.DownloadDataTransfer)
- assert.Equal(t, user.TotalDataTransfer, newUser.TotalDataTransfer)
- assert.Equal(t, int64(1000), newUser.Filters.MaxUploadFileSize)
- assert.Equal(t, user.AdditionalInfo, newUser.AdditionalInfo)
- assert.Equal(t, user.Description, newUser.Description)
- assert.True(t, newUser.Filters.Hooks.ExternalAuthDisabled)
- assert.False(t, newUser.Filters.Hooks.PreLoginDisabled)
- assert.False(t, newUser.Filters.Hooks.CheckPasswordDisabled)
- assert.True(t, newUser.Filters.DisableFsChecks)
- assert.False(t, newUser.Filters.AllowAPIKeyAuth)
- assert.Equal(t, user.Email, newUser.Email)
- assert.Equal(t, "/start/dir", newUser.Filters.StartDirectory)
- assert.Equal(t, 0, newUser.Filters.FTPSecurity)
- assert.Equal(t, 10, newUser.Filters.DefaultSharesExpiration)
- assert.Equal(t, 90, newUser.Filters.PasswordExpiration)
- assert.Greater(t, newUser.LastPasswordChange, int64(0))
- assert.True(t, newUser.Filters.RequirePasswordChange)
- assert.True(t, util.Contains(newUser.PublicKeys, testPubKey))
- if val, ok := newUser.Permissions["/subdir"]; ok {
- assert.True(t, util.Contains(val, dataprovider.PermListItems))
- assert.True(t, util.Contains(val, dataprovider.PermDownload))
- } else {
- assert.Fail(t, "user permissions must contain /somedir", "actual: %v", newUser.Permissions)
- }
- assert.Len(t, newUser.PublicKeys, 2)
- assert.Len(t, newUser.VirtualFolders, 1)
- for _, v := range newUser.VirtualFolders {
- assert.Equal(t, v.VirtualPath, "/vdir")
- assert.Equal(t, v.Name, folderName)
- assert.Equal(t, v.MappedPath, mappedDir)
- assert.Equal(t, v.QuotaFiles, 2)
- assert.Equal(t, v.QuotaSize, int64(1024))
- }
- assert.Len(t, newUser.Filters.FilePatterns, 3)
- for _, filter := range newUser.Filters.FilePatterns {
- switch filter.Path {
- case "/dir1":
- assert.Len(t, filter.DeniedPatterns, 1)
- assert.Len(t, filter.AllowedPatterns, 1)
- assert.True(t, util.Contains(filter.AllowedPatterns, "*.png"))
- assert.True(t, util.Contains(filter.DeniedPatterns, "*.zip"))
- assert.Equal(t, sdk.DenyPolicyDefault, filter.DenyPolicy)
- case "/dir2":
- assert.Len(t, filter.DeniedPatterns, 1)
- assert.Len(t, filter.AllowedPatterns, 2)
- assert.True(t, util.Contains(filter.AllowedPatterns, "*.jpg"))
- assert.True(t, util.Contains(filter.AllowedPatterns, "*.png"))
- assert.True(t, util.Contains(filter.DeniedPatterns, "*.mkv"))
- assert.Equal(t, sdk.DenyPolicyHide, filter.DenyPolicy)
- case "/dir3":
- assert.Len(t, filter.DeniedPatterns, 1)
- assert.Len(t, filter.AllowedPatterns, 0)
- assert.True(t, util.Contains(filter.DeniedPatterns, "*.rar"))
- assert.Equal(t, sdk.DenyPolicyDefault, filter.DenyPolicy)
- }
- }
- if assert.Len(t, newUser.Filters.BandwidthLimits, 2) {
- for _, bwLimit := range newUser.Filters.BandwidthLimits {
- if len(bwLimit.Sources) == 2 {
- assert.Equal(t, "192.168.1.0/24", bwLimit.Sources[0])
- assert.Equal(t, "192.168.2.0/25", bwLimit.Sources[1])
- assert.Equal(t, int64(256), bwLimit.UploadBandwidth)
- assert.Equal(t, int64(512), bwLimit.DownloadBandwidth)
- } else {
- assert.Equal(t, []string{"127.0.0.1/32"}, bwLimit.Sources)
- assert.Equal(t, int64(0), bwLimit.UploadBandwidth)
- assert.Equal(t, int64(1024), bwLimit.DownloadBandwidth)
- }
- }
- }
- if assert.Len(t, newUser.Filters.DataTransferLimits, 3) {
- for _, dtLimit := range newUser.Filters.DataTransferLimits {
- switch len(dtLimit.Sources) {
- case 3:
- assert.Equal(t, "192.168.3.0/24", dtLimit.Sources[0])
- assert.Equal(t, "10.8.2.0/24", dtLimit.Sources[1])
- assert.Equal(t, "::1/64", dtLimit.Sources[2])
- assert.Equal(t, int64(0), dtLimit.UploadDataTransfer)
- assert.Equal(t, int64(0), dtLimit.DownloadDataTransfer)
- assert.Equal(t, int64(200), dtLimit.TotalDataTransfer)
- case 2:
- assert.Equal(t, "192.168.5.0/24", dtLimit.Sources[0])
- assert.Equal(t, "10.8.0.0/16", dtLimit.Sources[1])
- assert.Equal(t, int64(120), dtLimit.UploadDataTransfer)
- assert.Equal(t, int64(100), dtLimit.DownloadDataTransfer)
- assert.Equal(t, int64(0), dtLimit.TotalDataTransfer)
- case 1:
- assert.Equal(t, "127.0.1.1/32", dtLimit.Sources[0])
- assert.Equal(t, int64(0), dtLimit.UploadDataTransfer)
- assert.Equal(t, int64(0), dtLimit.DownloadDataTransfer)
- assert.Equal(t, int64(0), dtLimit.TotalDataTransfer)
- }
- }
- }
- assert.Len(t, newUser.Groups, 3)
- assert.Equal(t, sdk.TLSUsernameNone, newUser.Filters.TLSUsername)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, newUser.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, err = httpdtest.RemoveGroup(group1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group2, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveGroup(group3, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestWebUserUpdateMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user := getTestUser()
- user.Filters.BandwidthLimits = []sdk.BandwidthLimit{
- {
- Sources: []string{"10.8.0.0/16", "192.168.1.0/25"},
- UploadBandwidth: 256,
- DownloadBandwidth: 512,
- },
- }
- user.TotalDataTransfer = 4000
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- lastPwdChange := user.LastPasswordChange
- assert.Greater(t, lastPwdChange, int64(0))
- // add TOTP config
- configName, _, secret, _, err := mfa.GenerateTOTPSecret(mfa.GetAvailableTOTPConfigNames()[0], user.Username)
- assert.NoError(t, err)
- userToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- userTOTPConfig := dataprovider.UserTOTPConfig{
- Enabled: true,
- ConfigName: configName,
- Secret: kms.NewPlainSecret(secret),
- Protocols: []string{common.ProtocolSSH, common.ProtocolFTP},
- }
- asJSON, err := json.Marshal(userTOTPConfig)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webClientTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setJWTCookieForReq(req, userToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Invalid token")
- req, err = http.NewRequest(http.MethodPost, webClientTOTPSavePath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setJWTCookieForReq(req, userToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.True(t, user.Filters.TOTPConfig.Enabled)
- assert.Equal(t, int64(4000), user.TotalDataTransfer)
- assert.Equal(t, lastPwdChange, user.LastPasswordChange)
- if assert.Len(t, user.Filters.BandwidthLimits, 1) {
- if assert.Len(t, user.Filters.BandwidthLimits[0].Sources, 2) {
- assert.Equal(t, "10.8.0.0/16", user.Filters.BandwidthLimits[0].Sources[0])
- assert.Equal(t, "192.168.1.0/25", user.Filters.BandwidthLimits[0].Sources[1])
- }
- assert.Equal(t, int64(256), user.Filters.BandwidthLimits[0].UploadBandwidth)
- assert.Equal(t, int64(512), user.Filters.BandwidthLimits[0].DownloadBandwidth)
- }
- dbUser, err := dataprovider.UserExists(user.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, dbUser.Password)
- assert.True(t, dbUser.IsPasswordHashed())
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- user.MaxSessions = 1
- user.QuotaFiles = 2
- user.QuotaSize = 1000 * 1000 * 1000
- user.GID = 1000
- user.Filters.AllowAPIKeyAuth = true
- user.AdditionalInfo = "new additional info"
- user.Email = "user@example.com"
- form := make(url.Values)
- form.Set("username", user.Username)
- form.Set("email", user.Email)
- form.Set("password", "")
- form.Set("public_keys", testPubKey)
- form.Set("home_dir", user.HomeDir)
- form.Set("uid", "0")
- form.Set("gid", strconv.FormatInt(int64(user.GID), 10))
- form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10))
- form.Set("quota_size", "1 GB")
- form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("permissions", "*")
- form.Set("sub_perm_path0", "/otherdir")
- form.Set("sub_perm_permissions0", "list")
- form.Add("sub_perm_permissions0", "upload")
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "2020-01-01 00:00:00")
- form.Set("allowed_ip", " 192.168.1.3/32, 192.168.2.0/24 ")
- form.Set("denied_ip", " 10.0.0.2/32 ")
- form.Set("pattern_path0", "/dir1")
- form.Set("patterns0", "*.zip")
- form.Set("pattern_type0", "denied")
- form.Set("denied_login_methods", dataprovider.SSHLoginMethodKeyboardInteractive)
- form.Set("denied_protocols", common.ProtocolFTP)
- form.Set("max_upload_file_size", "100")
- form.Set("default_shares_expiration", "30")
- form.Set("password_expiration", "60")
- form.Set("disconnect", "1")
- form.Set("additional_info", user.AdditionalInfo)
- form.Set("description", user.Description)
- form.Set("tls_username", string(sdk.TLSUsernameCN))
- form.Set("allow_api_key_auth", "1")
- form.Set("require_password_change", "1")
- form.Set("external_auth_cache_time", "120")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- dbUser, err = dataprovider.UserExists(user.Username, "")
- assert.NoError(t, err)
- assert.Empty(t, dbUser.Password)
- assert.False(t, dbUser.IsPasswordHashed())
- form.Set("password", defaultPassword)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- dbUser, err = dataprovider.UserExists(user.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, dbUser.Password)
- assert.True(t, dbUser.IsPasswordHashed())
- prevPwd := dbUser.Password
- form.Set("password", redactedSecret)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- dbUser, err = dataprovider.UserExists(user.Username, "")
- assert.NoError(t, err)
- assert.NotEmpty(t, dbUser.Password)
- assert.True(t, dbUser.IsPasswordHashed())
- assert.Equal(t, prevPwd, dbUser.Password)
- assert.True(t, dbUser.Filters.TOTPConfig.Enabled)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updateUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updateUser)
- assert.NoError(t, err)
- assert.Equal(t, user.Email, updateUser.Email)
- assert.Equal(t, user.HomeDir, updateUser.HomeDir)
- assert.Equal(t, user.MaxSessions, updateUser.MaxSessions)
- assert.Equal(t, user.QuotaFiles, updateUser.QuotaFiles)
- assert.Equal(t, user.QuotaSize, updateUser.QuotaSize)
- assert.Equal(t, user.UID, updateUser.UID)
- assert.Equal(t, user.GID, updateUser.GID)
- assert.Equal(t, user.AdditionalInfo, updateUser.AdditionalInfo)
- assert.Equal(t, user.Description, updateUser.Description)
- assert.Equal(t, int64(100), updateUser.Filters.MaxUploadFileSize)
- assert.Equal(t, sdk.TLSUsernameCN, updateUser.Filters.TLSUsername)
- assert.True(t, updateUser.Filters.AllowAPIKeyAuth)
- assert.True(t, updateUser.Filters.TOTPConfig.Enabled)
- assert.Equal(t, int64(0), updateUser.TotalDataTransfer)
- assert.Equal(t, int64(0), updateUser.DownloadDataTransfer)
- assert.Equal(t, int64(0), updateUser.UploadDataTransfer)
- assert.Equal(t, int64(0), updateUser.Filters.ExternalAuthCacheTime)
- assert.Equal(t, 30, updateUser.Filters.DefaultSharesExpiration)
- assert.Equal(t, 60, updateUser.Filters.PasswordExpiration)
- assert.True(t, updateUser.Filters.RequirePasswordChange)
- if val, ok := updateUser.Permissions["/otherdir"]; ok {
- assert.True(t, util.Contains(val, dataprovider.PermListItems))
- assert.True(t, util.Contains(val, dataprovider.PermUpload))
- } else {
- assert.Fail(t, "user permissions must contains /otherdir", "actual: %v", updateUser.Permissions)
- }
- assert.True(t, util.Contains(updateUser.Filters.AllowedIP, "192.168.1.3/32"))
- assert.True(t, util.Contains(updateUser.Filters.DeniedIP, "10.0.0.2/32"))
- assert.True(t, util.Contains(updateUser.Filters.DeniedLoginMethods, dataprovider.SSHLoginMethodKeyboardInteractive))
- assert.True(t, util.Contains(updateUser.Filters.DeniedProtocols, common.ProtocolFTP))
- assert.True(t, util.Contains(updateUser.Filters.FilePatterns[0].DeniedPatterns, "*.zip"))
- assert.Len(t, updateUser.Filters.BandwidthLimits, 0)
- req, err = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestRenderFolderTemplateMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webTemplateFolder, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- folder := vfs.BaseVirtualFolder{
- Name: "templatefolder",
- MappedPath: filepath.Join(os.TempDir(), "mapped"),
- Description: "template folder desc",
- }
- folder, _, err = httpdtest.AddFolder(folder, http.StatusCreated)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webTemplateFolder+fmt.Sprintf("?from=%v", folder.Name), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webTemplateFolder+"?from=unknown-folder", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestRenderUserTemplateMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webTemplateUser, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webTemplateUser+fmt.Sprintf("?from=%v", user.Username), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webTemplateUser+"?from=unknown", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserTemplateWithFoldersMock(t *testing.T) {
- folder := vfs.BaseVirtualFolder{
- Name: "vfolder",
- MappedPath: filepath.Join(os.TempDir(), "mapped"),
- Description: "vfolder desc with spéciàl ch@rs",
- }
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user := getTestUser()
- form := make(url.Values)
- form.Set("username", user.Username)
- form.Set("home_dir", filepath.Join(os.TempDir(), "%username%"))
- form.Set("uid", strconv.FormatInt(int64(user.UID), 10))
- form.Set("gid", strconv.FormatInt(int64(user.GID), 10))
- form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10))
- form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10))
- form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("permissions", "*")
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "2020-01-01 00:00:00")
- form.Set("fs_provider", "0")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- form.Set("ftp_security", "1")
- form.Set("external_auth_cache_time", "0")
- form.Set("description", "desc %username% %password%")
- form.Set("start_directory", "/base/%username%")
- form.Set("vfolder_path", "/vdir%username%")
- form.Set("vfolder_name", folder.Name)
- form.Set("vfolder_quota_size", "-1")
- form.Set("vfolder_quota_files", "-1")
- form.Add("tpl_username", "auser1")
- form.Add("tpl_password", "password1")
- form.Add("tpl_public_keys", " ")
- form.Add("tpl_username", "auser2")
- form.Add("tpl_password", "password2")
- form.Add("tpl_public_keys", testPubKey)
- form.Add("tpl_username", "auser1")
- form.Add("tpl_password", "password")
- form.Add("tpl_public_keys", "")
- form.Set("form_action", "export_from_template")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ := http.NewRequest(http.MethodPost, webTemplateUser, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- require.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateUser, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- require.Contains(t, rr.Body.String(), "invalid folder mapped path")
- folder, resp, err := httpdtest.AddFolder(folder, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateUser, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var dump dataprovider.BackupData
- err = json.Unmarshal(rr.Body.Bytes(), &dump)
- assert.NoError(t, err)
- assert.Len(t, dump.Users, 2)
- assert.Len(t, dump.Folders, 1)
- user1 := dump.Users[0]
- user2 := dump.Users[1]
- folder1 := dump.Folders[0]
- assert.Equal(t, "auser1", user1.Username)
- assert.Equal(t, "auser2", user2.Username)
- assert.Equal(t, "desc auser1 password1", user1.Description)
- assert.Equal(t, "desc auser2 password2", user2.Description)
- assert.Equal(t, filepath.Join(os.TempDir(), user1.Username), user1.HomeDir)
- assert.Equal(t, filepath.Join(os.TempDir(), user2.Username), user2.HomeDir)
- assert.Equal(t, path.Join("/base", user1.Username), user1.Filters.StartDirectory)
- assert.Equal(t, path.Join("/base", user2.Username), user2.Filters.StartDirectory)
- assert.Equal(t, 0, user2.Filters.DefaultSharesExpiration)
- assert.Equal(t, folder.Name, folder1.Name)
- assert.Equal(t, folder.MappedPath, folder1.MappedPath)
- assert.Equal(t, folder.Description, folder1.Description)
- assert.Len(t, user1.PublicKeys, 0)
- assert.Len(t, user2.PublicKeys, 1)
- assert.Len(t, user1.VirtualFolders, 1)
- assert.Len(t, user2.VirtualFolders, 1)
- assert.Equal(t, "/vdirauser1", user1.VirtualFolders[0].VirtualPath)
- assert.Equal(t, "/vdirauser2", user2.VirtualFolders[0].VirtualPath)
- assert.Equal(t, 1, user1.Filters.FTPSecurity)
- assert.Equal(t, 1, user2.Filters.FTPSecurity)
- _, err = httpdtest.RemoveFolder(folder, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserSaveFromTemplateMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user1 := "u1"
- user2 := "u2"
- form := make(url.Values)
- form.Set("username", "")
- form.Set("home_dir", filepath.Join(os.TempDir(), "%username%"))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("uid", "0")
- form.Set("gid", "0")
- form.Set("max_sessions", "0")
- form.Set("quota_size", "0")
- form.Set("quota_files", "0")
- form.Set("permissions", "*")
- form.Set("status", "1")
- form.Set("expiration_date", "")
- form.Set("fs_provider", "0")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- form.Set("external_auth_cache_time", "0")
- form.Add("tpl_username", user1)
- form.Add("tpl_password", "password1")
- form.Add("tpl_public_keys", " ")
- form.Add("tpl_username", user2)
- form.Add("tpl_public_keys", testPubKey)
- form.Set(csrfFormToken, csrfToken)
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ := http.NewRequest(http.MethodPost, webTemplateUser, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- u1, _, err := httpdtest.GetUserByUsername(user1, http.StatusOK)
- assert.NoError(t, err)
- u2, _, err := httpdtest.GetUserByUsername(user2, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(u1, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(u2, http.StatusOK)
- assert.NoError(t, err)
- err = dataprovider.Close()
- assert.NoError(t, err)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, err = http.NewRequest(http.MethodPost, webTemplateUser, &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- assert.Contains(t, rr.Body.String(), "Cannot save the defined users")
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- }
- func TestUserTemplateMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- user := getTestUser()
- user.FsConfig.Provider = sdk.S3FilesystemProvider
- user.FsConfig.S3Config.Bucket = "test"
- user.FsConfig.S3Config.Region = "eu-central-1"
- user.FsConfig.S3Config.AccessKey = "%username%"
- user.FsConfig.S3Config.KeyPrefix = "somedir/subdir/"
- user.FsConfig.S3Config.UploadPartSize = 5
- user.FsConfig.S3Config.UploadConcurrency = 4
- user.FsConfig.S3Config.DownloadPartSize = 6
- user.FsConfig.S3Config.DownloadConcurrency = 3
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- form.Set("home_dir", filepath.Join(os.TempDir(), "%username%"))
- form.Set("uid", "0")
- form.Set("gid", strconv.FormatInt(int64(user.GID), 10))
- form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10))
- form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10))
- form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("permissions", "*")
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "2020-01-01 00:00:00")
- form.Set("allowed_ip", "")
- form.Set("denied_ip", "")
- form.Set("fs_provider", "1")
- form.Set("s3_bucket", user.FsConfig.S3Config.Bucket)
- form.Set("s3_region", user.FsConfig.S3Config.Region)
- form.Set("s3_access_key", "%username%")
- form.Set("s3_access_secret", "%password%")
- form.Set("s3_key_prefix", "base/%username%")
- form.Set("allowed_extensions", "/dir1::.jpg,.png")
- form.Set("denied_extensions", "/dir2::.zip")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- form.Add("hooks", "external_auth_disabled")
- form.Add("hooks", "check_password_disabled")
- form.Set("disable_fs_checks", "checked")
- form.Set("s3_download_part_max_time", "0")
- form.Set("s3_upload_part_max_time", "0")
- // test invalid s3_upload_part_size
- form.Set("s3_upload_part_size", "a")
- form.Set("form_action", "export_from_template")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ := http.NewRequest(http.MethodPost, webTemplateUser, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- form.Set("s3_upload_part_size", strconv.FormatInt(user.FsConfig.S3Config.UploadPartSize, 10))
- form.Set("s3_upload_concurrency", strconv.Itoa(user.FsConfig.S3Config.UploadConcurrency))
- form.Set("s3_download_part_size", strconv.FormatInt(user.FsConfig.S3Config.DownloadPartSize, 10))
- form.Set("s3_download_concurrency", strconv.Itoa(user.FsConfig.S3Config.DownloadConcurrency))
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateUser, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- form.Set("tpl_username", "user1")
- form.Set("tpl_password", "password1")
- form.Set("tpl_public_keys", "invalid-pkey")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateUser, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- require.Contains(t, rr.Body.String(), "Error validating user")
- form.Set("tpl_username", " ")
- form.Set("tpl_password", "pwd")
- form.Set("tpl_public_keys", testPubKey)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateUser, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- require.Contains(t, rr.Body.String(), "No valid users defined, unable to complete the requested action")
- form.Set("tpl_username", "user1")
- form.Set("tpl_password", "password1")
- form.Set("tpl_public_keys", " ")
- form.Add("tpl_username", "user2")
- form.Add("tpl_password", "password2")
- form.Add("tpl_public_keys", testPubKey)
- form.Add("tpl_username", "")
- form.Add("tpl_password", "password3")
- form.Add("tpl_public_keys", testPubKey)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateUser, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var dump dataprovider.BackupData
- err = json.Unmarshal(rr.Body.Bytes(), &dump)
- require.NoError(t, err)
- require.Len(t, dump.Users, 2)
- require.Len(t, dump.Admins, 0)
- require.Len(t, dump.Folders, 0)
- user1 := dump.Users[0]
- user2 := dump.Users[1]
- require.Equal(t, "user1", user1.Username)
- require.Equal(t, sdk.S3FilesystemProvider, user1.FsConfig.Provider)
- require.Equal(t, "user2", user2.Username)
- require.Equal(t, sdk.S3FilesystemProvider, user2.FsConfig.Provider)
- require.Len(t, user2.PublicKeys, 1)
- require.Equal(t, filepath.Join(os.TempDir(), user1.Username), user1.HomeDir)
- require.Equal(t, filepath.Join(os.TempDir(), user2.Username), user2.HomeDir)
- require.Equal(t, user1.Username, user1.FsConfig.S3Config.AccessKey)
- require.Equal(t, user2.Username, user2.FsConfig.S3Config.AccessKey)
- require.Equal(t, path.Join("base", user1.Username)+"/", user1.FsConfig.S3Config.KeyPrefix)
- require.Equal(t, path.Join("base", user2.Username)+"/", user2.FsConfig.S3Config.KeyPrefix)
- require.True(t, user1.FsConfig.S3Config.AccessSecret.IsEncrypted())
- err = user1.FsConfig.S3Config.AccessSecret.Decrypt()
- require.NoError(t, err)
- require.Equal(t, "password1", user1.FsConfig.S3Config.AccessSecret.GetPayload())
- require.True(t, user2.FsConfig.S3Config.AccessSecret.IsEncrypted())
- err = user2.FsConfig.S3Config.AccessSecret.Decrypt()
- require.NoError(t, err)
- require.Equal(t, "password2", user2.FsConfig.S3Config.AccessSecret.GetPayload())
- require.True(t, user1.Filters.Hooks.ExternalAuthDisabled)
- require.True(t, user1.Filters.Hooks.CheckPasswordDisabled)
- require.False(t, user1.Filters.Hooks.PreLoginDisabled)
- require.True(t, user2.Filters.Hooks.ExternalAuthDisabled)
- require.True(t, user2.Filters.Hooks.CheckPasswordDisabled)
- require.False(t, user2.Filters.Hooks.PreLoginDisabled)
- require.True(t, user1.Filters.DisableFsChecks)
- require.True(t, user2.Filters.DisableFsChecks)
- }
- func TestUserPlaceholders(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- u := getTestUser()
- u.HomeDir = filepath.Join(os.TempDir(), "%username%_%password%")
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", u.Username)
- form.Set("home_dir", u.HomeDir)
- form.Set("password", u.Password)
- form.Set("status", strconv.Itoa(u.Status))
- form.Set("expiration_date", "")
- form.Set("permissions", "*")
- form.Set("public_keys", testPubKey)
- form.Add("public_keys", testPubKey1)
- form.Set("uid", "0")
- form.Set("gid", "0")
- form.Set("max_sessions", "0")
- form.Set("quota_size", "0")
- form.Set("quota_files", "0")
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("total_data_transfer", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ := http.NewRequest(http.MethodPost, webUserPath, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- user, _, err := httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, filepath.Join(os.TempDir(), fmt.Sprintf("%v_%v", defaultUsername, defaultPassword)), user.HomeDir)
- dbUser, err := dataprovider.UserExists(defaultUsername, "")
- assert.NoError(t, err)
- assert.True(t, dbUser.IsPasswordHashed())
- hashedPwd := dbUser.Password
- form.Set("password", redactedSecret)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, err = http.NewRequest(http.MethodPost, path.Join(webUserPath, defaultUsername), &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, filepath.Join(os.TempDir(), defaultUsername+"_%password%"), user.HomeDir)
- // check that the password was unchanged
- dbUser, err = dataprovider.UserExists(defaultUsername, "")
- assert.NoError(t, err)
- assert.True(t, dbUser.IsPasswordHashed())
- assert.Equal(t, hashedPwd, dbUser.Password)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestFolderPlaceholders(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- folderName := "folderName"
- form := make(url.Values)
- form.Set("name", folderName)
- form.Set("mapped_path", filepath.Join(os.TempDir(), "%name%"))
- form.Set("description", "desc folder %name%")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, err := http.NewRequest(http.MethodPost, webFolderPath, &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- folderGet, _, err := httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, filepath.Join(os.TempDir(), folderName), folderGet.MappedPath)
- assert.Equal(t, fmt.Sprintf("desc folder %v", folderName), folderGet.Description)
- form.Set("mapped_path", filepath.Join(os.TempDir(), "%name%_%name%"))
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, err = http.NewRequest(http.MethodPost, path.Join(webFolderPath, folderName), &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- folderGet, _, err = httpdtest.GetFolderByName(folderName, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, filepath.Join(os.TempDir(), fmt.Sprintf("%v_%v", folderName, folderName)), folderGet.MappedPath)
- assert.Equal(t, fmt.Sprintf("desc folder %v", folderName), folderGet.Description)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folderName}, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestFolderSaveFromTemplateMock(t *testing.T) {
- folder1 := "f1"
- folder2 := "f2"
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("name", "name")
- form.Set("mapped_path", filepath.Join(os.TempDir(), "%name%"))
- form.Set("description", "desc folder %name%")
- form.Add("tpl_foldername", folder1)
- form.Add("tpl_foldername", folder2)
- form.Set(csrfFormToken, csrfToken)
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, err := http.NewRequest(http.MethodPost, webTemplateFolder, &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- _, _, err = httpdtest.GetFolderByName(folder1, http.StatusOK)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetFolderByName(folder2, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folder1}, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveFolder(vfs.BaseVirtualFolder{Name: folder2}, http.StatusOK)
- assert.NoError(t, err)
- err = dataprovider.Close()
- assert.NoError(t, err)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, err = http.NewRequest(http.MethodPost, webTemplateFolder, &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- assert.Contains(t, rr.Body.String(), "Cannot save the defined folders")
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- }
- func TestFolderTemplateMock(t *testing.T) {
- folderName := "vfolder-template"
- mappedPath := filepath.Join(os.TempDir(), "%name%mapped%name%path")
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("name", folderName)
- form.Set("mapped_path", mappedPath)
- form.Set("description", "desc folder %name%")
- form.Add("tpl_foldername", "folder1")
- form.Add("tpl_foldername", "folder2")
- form.Add("tpl_foldername", "folder3")
- form.Add("tpl_foldername", "folder1 ")
- form.Add("tpl_foldername", " ")
- form.Set("form_action", "export_from_template")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ := http.NewRequest(http.MethodPost, webTemplateFolder, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateFolder+"?param=p%C3%AO%GG", &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Error parsing folders fields")
- folder1 := "folder1"
- folder2 := "folder2"
- folder3 := "folder3"
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateFolder, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var dump dataprovider.BackupData
- err = json.Unmarshal(rr.Body.Bytes(), &dump)
- require.NoError(t, err)
- require.Len(t, dump.Users, 0)
- require.Len(t, dump.Admins, 0)
- require.Len(t, dump.Folders, 3)
- require.Equal(t, folder1, dump.Folders[0].Name)
- require.Equal(t, "desc folder folder1", dump.Folders[0].Description)
- require.True(t, strings.HasSuffix(dump.Folders[0].MappedPath, "folder1mappedfolder1path"))
- require.Equal(t, folder2, dump.Folders[1].Name)
- require.Equal(t, "desc folder folder2", dump.Folders[1].Description)
- require.True(t, strings.HasSuffix(dump.Folders[1].MappedPath, "folder2mappedfolder2path"))
- require.Equal(t, folder3, dump.Folders[2].Name)
- require.Equal(t, "desc folder folder3", dump.Folders[2].Description)
- require.True(t, strings.HasSuffix(dump.Folders[2].MappedPath, "folder3mappedfolder3path"))
- form.Set("fs_provider", "1")
- form.Set("s3_bucket", "bucket")
- form.Set("s3_region", "us-east-1")
- form.Set("s3_access_key", "%name%")
- form.Set("s3_access_secret", "pwd%name%")
- form.Set("s3_key_prefix", "base/%name%")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateFolder, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Error parsing folders fields")
- form.Set("s3_upload_part_size", "5")
- form.Set("s3_upload_concurrency", "4")
- form.Set("s3_download_part_max_time", "0")
- form.Set("s3_upload_part_max_time", "0")
- form.Set("s3_download_part_size", "6")
- form.Set("s3_download_concurrency", "2")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateFolder, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- dump = dataprovider.BackupData{}
- err = json.Unmarshal(rr.Body.Bytes(), &dump)
- require.NoError(t, err)
- require.Len(t, dump.Users, 0)
- require.Len(t, dump.Admins, 0)
- require.Len(t, dump.Folders, 3)
- require.Equal(t, folder1, dump.Folders[0].Name)
- require.Equal(t, folder1, dump.Folders[0].FsConfig.S3Config.AccessKey)
- err = dump.Folders[0].FsConfig.S3Config.AccessSecret.Decrypt()
- require.NoError(t, err)
- require.Equal(t, "pwd"+folder1, dump.Folders[0].FsConfig.S3Config.AccessSecret.GetPayload())
- require.Equal(t, "base/"+folder1+"/", dump.Folders[0].FsConfig.S3Config.KeyPrefix)
- require.Equal(t, folder2, dump.Folders[1].Name)
- require.Equal(t, folder2, dump.Folders[1].FsConfig.S3Config.AccessKey)
- err = dump.Folders[1].FsConfig.S3Config.AccessSecret.Decrypt()
- require.NoError(t, err)
- require.Equal(t, "pwd"+folder2, dump.Folders[1].FsConfig.S3Config.AccessSecret.GetPayload())
- require.Equal(t, "base/"+folder2+"/", dump.Folders[1].FsConfig.S3Config.KeyPrefix)
- require.Equal(t, folder3, dump.Folders[2].Name)
- require.Equal(t, folder3, dump.Folders[2].FsConfig.S3Config.AccessKey)
- err = dump.Folders[2].FsConfig.S3Config.AccessSecret.Decrypt()
- require.NoError(t, err)
- require.Equal(t, "pwd"+folder3, dump.Folders[2].FsConfig.S3Config.AccessSecret.GetPayload())
- require.Equal(t, "base/"+folder3+"/", dump.Folders[2].FsConfig.S3Config.KeyPrefix)
- form.Set("tpl_foldername", " ")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateFolder, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "No valid folders defined")
- form.Set("tpl_foldername", "name")
- form.Set("mapped_path", "relative-path")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, webTemplateFolder, &b)
- setJWTCookieForReq(req, token)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Error validating folder")
- }
- func TestWebUserS3Mock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- lastPwdChange := user.LastPasswordChange
- assert.Greater(t, lastPwdChange, int64(0))
- user.FsConfig.Provider = sdk.S3FilesystemProvider
- user.FsConfig.S3Config.Bucket = "test"
- user.FsConfig.S3Config.Region = "eu-west-1"
- user.FsConfig.S3Config.AccessKey = "access-key"
- user.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("access-secret")
- user.FsConfig.S3Config.RoleARN = "arn:aws:iam::123456789012:user/Development/product_1234/*"
- user.FsConfig.S3Config.Endpoint = "http://127.0.0.1:9000/path?a=b"
- user.FsConfig.S3Config.StorageClass = "Standard"
- user.FsConfig.S3Config.KeyPrefix = "somedir/subdir/"
- user.FsConfig.S3Config.UploadPartSize = 5
- user.FsConfig.S3Config.UploadConcurrency = 4
- user.FsConfig.S3Config.DownloadPartMaxTime = 60
- user.FsConfig.S3Config.UploadPartMaxTime = 120
- user.FsConfig.S3Config.DownloadPartSize = 6
- user.FsConfig.S3Config.DownloadConcurrency = 3
- user.FsConfig.S3Config.ForcePathStyle = true
- user.FsConfig.S3Config.ACL = "public-read"
- user.Description = "s3 tèst user"
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- form.Set("password", redactedSecret)
- form.Set("home_dir", user.HomeDir)
- form.Set("uid", "0")
- form.Set("gid", strconv.FormatInt(int64(user.GID), 10))
- form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10))
- form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10))
- form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("permissions", "*")
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "2020-01-01 00:00:00")
- form.Set("allowed_ip", "")
- form.Set("denied_ip", "")
- form.Set("fs_provider", "1")
- form.Set("s3_bucket", user.FsConfig.S3Config.Bucket)
- form.Set("s3_region", user.FsConfig.S3Config.Region)
- form.Set("s3_access_key", user.FsConfig.S3Config.AccessKey)
- form.Set("s3_access_secret", user.FsConfig.S3Config.AccessSecret.GetPayload())
- form.Set("s3_role_arn", user.FsConfig.S3Config.RoleARN)
- form.Set("s3_storage_class", user.FsConfig.S3Config.StorageClass)
- form.Set("s3_acl", user.FsConfig.S3Config.ACL)
- form.Set("s3_endpoint", user.FsConfig.S3Config.Endpoint)
- form.Set("s3_key_prefix", user.FsConfig.S3Config.KeyPrefix)
- form.Set("pattern_path0", "/dir1")
- form.Set("patterns0", "*.jpg,*.png")
- form.Set("pattern_type0", "allowed")
- form.Set("pattern_policy0", "0")
- form.Set("pattern_path1", "/dir2")
- form.Set("patterns1", "*.zip")
- form.Set("pattern_type1", "denied")
- form.Set("pattern_policy1", "1")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- form.Set("ftp_security", "1")
- form.Set("s3_force_path_style", "checked")
- form.Set("description", user.Description)
- form.Add("hooks", "pre_login_disabled")
- form.Add("allow_api_key_auth", "1")
- // test invalid s3_upload_part_size
- form.Set("s3_upload_part_size", "a")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // test invalid s3_upload_concurrency
- form.Set("s3_upload_part_size", strconv.FormatInt(user.FsConfig.S3Config.UploadPartSize, 10))
- form.Set("s3_upload_concurrency", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // test invalid s3_download_part_size
- form.Set("s3_upload_concurrency", strconv.Itoa(user.FsConfig.S3Config.UploadConcurrency))
- form.Set("s3_download_part_size", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // test invalid s3_download_concurrency
- form.Set("s3_download_part_size", strconv.FormatInt(user.FsConfig.S3Config.DownloadPartSize, 10))
- form.Set("s3_download_concurrency", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // test invalid s3_download_part_max_time
- form.Set("s3_download_concurrency", strconv.Itoa(user.FsConfig.S3Config.DownloadConcurrency))
- form.Set("s3_download_part_max_time", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // test invalid s3_upload_part_max_time
- form.Set("s3_download_part_max_time", strconv.Itoa(user.FsConfig.S3Config.DownloadPartMaxTime))
- form.Set("s3_upload_part_max_time", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now add the user
- form.Set("s3_upload_part_max_time", strconv.Itoa(user.FsConfig.S3Config.UploadPartMaxTime))
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updateUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updateUser)
- assert.NoError(t, err)
- assert.Equal(t, int64(1577836800000), updateUser.ExpirationDate)
- assert.Equal(t, updateUser.FsConfig.S3Config.Bucket, user.FsConfig.S3Config.Bucket)
- assert.Equal(t, updateUser.FsConfig.S3Config.Region, user.FsConfig.S3Config.Region)
- assert.Equal(t, updateUser.FsConfig.S3Config.AccessKey, user.FsConfig.S3Config.AccessKey)
- assert.Equal(t, updateUser.FsConfig.S3Config.RoleARN, user.FsConfig.S3Config.RoleARN)
- assert.Equal(t, updateUser.FsConfig.S3Config.StorageClass, user.FsConfig.S3Config.StorageClass)
- assert.Equal(t, updateUser.FsConfig.S3Config.ACL, user.FsConfig.S3Config.ACL)
- assert.Equal(t, updateUser.FsConfig.S3Config.Endpoint, user.FsConfig.S3Config.Endpoint)
- assert.Equal(t, updateUser.FsConfig.S3Config.KeyPrefix, user.FsConfig.S3Config.KeyPrefix)
- assert.Equal(t, updateUser.FsConfig.S3Config.UploadPartSize, user.FsConfig.S3Config.UploadPartSize)
- assert.Equal(t, updateUser.FsConfig.S3Config.UploadConcurrency, user.FsConfig.S3Config.UploadConcurrency)
- assert.Equal(t, updateUser.FsConfig.S3Config.DownloadPartMaxTime, user.FsConfig.S3Config.DownloadPartMaxTime)
- assert.Equal(t, updateUser.FsConfig.S3Config.UploadPartMaxTime, user.FsConfig.S3Config.UploadPartMaxTime)
- assert.Equal(t, updateUser.FsConfig.S3Config.DownloadPartSize, user.FsConfig.S3Config.DownloadPartSize)
- assert.Equal(t, updateUser.FsConfig.S3Config.DownloadConcurrency, user.FsConfig.S3Config.DownloadConcurrency)
- assert.Equal(t, lastPwdChange, updateUser.LastPasswordChange)
- assert.True(t, updateUser.FsConfig.S3Config.ForcePathStyle)
- if assert.Equal(t, 2, len(updateUser.Filters.FilePatterns)) {
- for _, filter := range updateUser.Filters.FilePatterns {
- switch filter.Path {
- case "/dir1":
- assert.Equal(t, sdk.DenyPolicyDefault, filter.DenyPolicy)
- case "/dir2":
- assert.Equal(t, sdk.DenyPolicyHide, filter.DenyPolicy)
- }
- }
- }
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateUser.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.NotEmpty(t, updateUser.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Empty(t, updateUser.FsConfig.S3Config.AccessSecret.GetKey())
- assert.Empty(t, updateUser.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- assert.Equal(t, user.Description, updateUser.Description)
- assert.True(t, updateUser.Filters.Hooks.PreLoginDisabled)
- assert.False(t, updateUser.Filters.Hooks.ExternalAuthDisabled)
- assert.False(t, updateUser.Filters.Hooks.CheckPasswordDisabled)
- assert.False(t, updateUser.Filters.DisableFsChecks)
- assert.True(t, updateUser.Filters.AllowAPIKeyAuth)
- assert.Equal(t, 1, updateUser.Filters.FTPSecurity)
- // now check that a redacted password is not saved
- form.Set("s3_access_secret", redactedSecret)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var lastUpdatedUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &lastUpdatedUser)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.S3Config.AccessSecret.GetStatus())
- assert.Equal(t, updateUser.FsConfig.S3Config.AccessSecret.GetPayload(), lastUpdatedUser.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Empty(t, lastUpdatedUser.FsConfig.S3Config.AccessSecret.GetKey())
- assert.Empty(t, lastUpdatedUser.FsConfig.S3Config.AccessSecret.GetAdditionalData())
- assert.Equal(t, lastPwdChange, lastUpdatedUser.LastPasswordChange)
- // now clear credentials
- form.Set("s3_access_key", "")
- form.Set("s3_access_secret", "")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var userGet dataprovider.User
- err = render.DecodeJSON(rr.Body, &userGet)
- assert.NoError(t, err)
- assert.Nil(t, userGet.FsConfig.S3Config.AccessSecret)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestWebUserGCSMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, err := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- credentialsFilePath := filepath.Join(os.TempDir(), "gcs.json")
- err = createTestFile(credentialsFilePath, 0)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.GCSFilesystemProvider
- user.FsConfig.GCSConfig.Bucket = "test"
- user.FsConfig.GCSConfig.KeyPrefix = "somedir/subdir/"
- user.FsConfig.GCSConfig.StorageClass = "standard"
- user.FsConfig.GCSConfig.ACL = "publicReadWrite"
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- form.Set("password", redactedSecret)
- form.Set("home_dir", user.HomeDir)
- form.Set("uid", "0")
- form.Set("gid", strconv.FormatInt(int64(user.GID), 10))
- form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10))
- form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10))
- form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("permissions", "*")
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "2020-01-01 00:00:00")
- form.Set("allowed_ip", "")
- form.Set("denied_ip", "")
- form.Set("fs_provider", "2")
- form.Set("gcs_bucket", user.FsConfig.GCSConfig.Bucket)
- form.Set("gcs_storage_class", user.FsConfig.GCSConfig.StorageClass)
- form.Set("gcs_acl", user.FsConfig.GCSConfig.ACL)
- form.Set("gcs_key_prefix", user.FsConfig.GCSConfig.KeyPrefix)
- form.Set("pattern_path0", "/dir1")
- form.Set("patterns0", "*.jpg,*.png")
- form.Set("pattern_type0", "allowed")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- form.Set("ftp_security", "1")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- b, contentType, _ = getMultipartFormData(form, "gcs_credential_file", credentialsFilePath)
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = createTestFile(credentialsFilePath, 4096)
- assert.NoError(t, err)
- b, contentType, _ = getMultipartFormData(form, "gcs_credential_file", credentialsFilePath)
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updateUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updateUser)
- assert.NoError(t, err)
- assert.Equal(t, int64(1577836800000), updateUser.ExpirationDate)
- assert.Equal(t, user.FsConfig.Provider, updateUser.FsConfig.Provider)
- assert.Equal(t, user.FsConfig.GCSConfig.Bucket, updateUser.FsConfig.GCSConfig.Bucket)
- assert.Equal(t, user.FsConfig.GCSConfig.StorageClass, updateUser.FsConfig.GCSConfig.StorageClass)
- assert.Equal(t, user.FsConfig.GCSConfig.ACL, updateUser.FsConfig.GCSConfig.ACL)
- assert.Equal(t, user.FsConfig.GCSConfig.KeyPrefix, updateUser.FsConfig.GCSConfig.KeyPrefix)
- if assert.Len(t, updateUser.Filters.FilePatterns, 1) {
- assert.Equal(t, "/dir1", updateUser.Filters.FilePatterns[0].Path)
- assert.Len(t, updateUser.Filters.FilePatterns[0].AllowedPatterns, 2)
- assert.Contains(t, updateUser.Filters.FilePatterns[0].AllowedPatterns, "*.png")
- assert.Contains(t, updateUser.Filters.FilePatterns[0].AllowedPatterns, "*.jpg")
- }
- assert.Equal(t, 1, updateUser.Filters.FTPSecurity)
- form.Set("gcs_auto_credentials", "on")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- updateUser = dataprovider.User{}
- err = render.DecodeJSON(rr.Body, &updateUser)
- assert.NoError(t, err)
- assert.Equal(t, 1, updateUser.FsConfig.GCSConfig.AutomaticCredentials)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = os.Remove(credentialsFilePath)
- assert.NoError(t, err)
- }
- func TestWebUserHTTPFsMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, err := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.HTTPFilesystemProvider
- user.FsConfig.HTTPConfig = vfs.HTTPFsConfig{
- BaseHTTPFsConfig: sdk.BaseHTTPFsConfig{
- Endpoint: "https://127.0.0.1:9999/api/v1",
- Username: defaultUsername,
- SkipTLSVerify: true,
- },
- Password: kms.NewPlainSecret(defaultPassword),
- APIKey: kms.NewPlainSecret(defaultTokenAuthPass),
- }
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- form.Set("password", redactedSecret)
- form.Set("home_dir", user.HomeDir)
- form.Set("uid", "0")
- form.Set("gid", strconv.FormatInt(int64(user.GID), 10))
- form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10))
- form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10))
- form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("permissions", "*")
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "2020-01-01 00:00:00")
- form.Set("allowed_ip", "")
- form.Set("denied_ip", "")
- form.Set("fs_provider", "6")
- form.Set("http_endpoint", user.FsConfig.HTTPConfig.Endpoint)
- form.Set("http_username", user.FsConfig.HTTPConfig.Username)
- form.Set("http_password", user.FsConfig.HTTPConfig.Password.GetPayload())
- form.Set("http_api_key", user.FsConfig.HTTPConfig.APIKey.GetPayload())
- form.Set("http_skip_tls_verify", "checked")
- form.Set("pattern_path0", "/dir1")
- form.Set("patterns0", "*.jpg,*.png")
- form.Set("pattern_type0", "allowed")
- form.Set("pattern_path1", "/dir2")
- form.Set("patterns1", "*.zip")
- form.Set("pattern_type1", "denied")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- form.Set("http_equality_check_mode", "true")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // check the updated user
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updateUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updateUser)
- assert.NoError(t, err)
- assert.Equal(t, int64(1577836800000), updateUser.ExpirationDate)
- assert.Equal(t, 2, len(updateUser.Filters.FilePatterns))
- assert.Equal(t, user.FsConfig.HTTPConfig.Endpoint, updateUser.FsConfig.HTTPConfig.Endpoint)
- assert.Equal(t, user.FsConfig.HTTPConfig.Username, updateUser.FsConfig.HTTPConfig.Username)
- assert.Equal(t, user.FsConfig.HTTPConfig.SkipTLSVerify, updateUser.FsConfig.HTTPConfig.SkipTLSVerify)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateUser.FsConfig.HTTPConfig.Password.GetStatus())
- assert.NotEmpty(t, updateUser.FsConfig.HTTPConfig.Password.GetPayload())
- assert.Empty(t, updateUser.FsConfig.HTTPConfig.Password.GetKey())
- assert.Empty(t, updateUser.FsConfig.HTTPConfig.Password.GetAdditionalData())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateUser.FsConfig.HTTPConfig.APIKey.GetStatus())
- assert.NotEmpty(t, updateUser.FsConfig.HTTPConfig.APIKey.GetPayload())
- assert.Empty(t, updateUser.FsConfig.HTTPConfig.APIKey.GetKey())
- assert.Empty(t, updateUser.FsConfig.HTTPConfig.APIKey.GetAdditionalData())
- assert.Equal(t, 1, updateUser.FsConfig.HTTPConfig.EqualityCheckMode)
- // now check that a redacted password is not saved
- form.Set("http_equality_check_mode", "")
- form.Set("http_password", " "+redactedSecret+" ")
- form.Set("http_api_key", redactedSecret)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var lastUpdatedUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &lastUpdatedUser)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.HTTPConfig.Password.GetStatus())
- assert.Equal(t, updateUser.FsConfig.HTTPConfig.Password.GetPayload(), lastUpdatedUser.FsConfig.HTTPConfig.Password.GetPayload())
- assert.Empty(t, lastUpdatedUser.FsConfig.HTTPConfig.Password.GetKey())
- assert.Empty(t, lastUpdatedUser.FsConfig.HTTPConfig.Password.GetAdditionalData())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.HTTPConfig.APIKey.GetStatus())
- assert.Equal(t, updateUser.FsConfig.HTTPConfig.APIKey.GetPayload(), lastUpdatedUser.FsConfig.HTTPConfig.APIKey.GetPayload())
- assert.Empty(t, lastUpdatedUser.FsConfig.HTTPConfig.APIKey.GetKey())
- assert.Empty(t, lastUpdatedUser.FsConfig.HTTPConfig.APIKey.GetAdditionalData())
- assert.Equal(t, 0, lastUpdatedUser.FsConfig.HTTPConfig.EqualityCheckMode)
- req, err = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestWebUserAzureBlobMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.AzureBlobFilesystemProvider
- user.FsConfig.AzBlobConfig.Container = "container"
- user.FsConfig.AzBlobConfig.AccountName = "aname"
- user.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret("access-skey")
- user.FsConfig.AzBlobConfig.Endpoint = "http://127.0.0.1:9000/path?b=c"
- user.FsConfig.AzBlobConfig.KeyPrefix = "somedir/subdir/"
- user.FsConfig.AzBlobConfig.UploadPartSize = 5
- user.FsConfig.AzBlobConfig.UploadConcurrency = 4
- user.FsConfig.AzBlobConfig.DownloadPartSize = 3
- user.FsConfig.AzBlobConfig.DownloadConcurrency = 6
- user.FsConfig.AzBlobConfig.UseEmulator = true
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- form.Set("password", redactedSecret)
- form.Set("home_dir", user.HomeDir)
- form.Set("uid", "0")
- form.Set("gid", strconv.FormatInt(int64(user.GID), 10))
- form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10))
- form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10))
- form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("permissions", "*")
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "2020-01-01 00:00:00")
- form.Set("allowed_ip", "")
- form.Set("denied_ip", "")
- form.Set("fs_provider", "3")
- form.Set("az_container", user.FsConfig.AzBlobConfig.Container)
- form.Set("az_account_name", user.FsConfig.AzBlobConfig.AccountName)
- form.Set("az_account_key", user.FsConfig.AzBlobConfig.AccountKey.GetPayload())
- form.Set("az_endpoint", user.FsConfig.AzBlobConfig.Endpoint)
- form.Set("az_key_prefix", user.FsConfig.AzBlobConfig.KeyPrefix)
- form.Set("az_use_emulator", "checked")
- form.Set("pattern_path0", "/dir1")
- form.Set("patterns0", "*.jpg,*.png")
- form.Set("pattern_type0", "allowed")
- form.Set("pattern_path1", "/dir2")
- form.Set("patterns1", "*.zip")
- form.Set("pattern_type1", "denied")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- // test invalid az_upload_part_size
- form.Set("az_upload_part_size", "a")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // test invalid az_upload_concurrency
- form.Set("az_upload_part_size", strconv.FormatInt(user.FsConfig.AzBlobConfig.UploadPartSize, 10))
- form.Set("az_upload_concurrency", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // test invalid az_download_part_size
- form.Set("az_upload_concurrency", strconv.Itoa(user.FsConfig.AzBlobConfig.UploadConcurrency))
- form.Set("az_download_part_size", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // test invalid az_download_concurrency
- form.Set("az_download_part_size", strconv.FormatInt(user.FsConfig.AzBlobConfig.DownloadPartSize, 10))
- form.Set("az_download_concurrency", "a")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now add the user
- form.Set("az_download_concurrency", strconv.Itoa(user.FsConfig.AzBlobConfig.DownloadConcurrency))
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updateUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updateUser)
- assert.NoError(t, err)
- assert.Equal(t, int64(1577836800000), updateUser.ExpirationDate)
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.Container, user.FsConfig.AzBlobConfig.Container)
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.AccountName, user.FsConfig.AzBlobConfig.AccountName)
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.Endpoint, user.FsConfig.AzBlobConfig.Endpoint)
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.KeyPrefix, user.FsConfig.AzBlobConfig.KeyPrefix)
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadPartSize, user.FsConfig.AzBlobConfig.UploadPartSize)
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.UploadConcurrency, user.FsConfig.AzBlobConfig.UploadConcurrency)
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.DownloadPartSize, user.FsConfig.AzBlobConfig.DownloadPartSize)
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.DownloadConcurrency, user.FsConfig.AzBlobConfig.DownloadConcurrency)
- assert.Equal(t, 2, len(updateUser.Filters.FilePatterns))
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateUser.FsConfig.AzBlobConfig.AccountKey.GetStatus())
- assert.NotEmpty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetPayload())
- assert.Empty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetKey())
- assert.Empty(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
- // now check that a redacted password is not saved
- form.Set("az_account_key", redactedSecret+" ")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var lastUpdatedUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &lastUpdatedUser)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetStatus())
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.AccountKey.GetPayload(), lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetPayload())
- assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetKey())
- assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.AccountKey.GetAdditionalData())
- // test SAS url
- user.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret("sasurl")
- form.Set("az_account_name", "")
- form.Set("az_account_key", "")
- form.Set("az_container", "")
- form.Set("az_sas_url", user.FsConfig.AzBlobConfig.SASURL.GetPayload())
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- updateUser = dataprovider.User{}
- err = render.DecodeJSON(rr.Body, &updateUser)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateUser.FsConfig.AzBlobConfig.SASURL.GetStatus())
- assert.NotEmpty(t, updateUser.FsConfig.AzBlobConfig.SASURL.GetPayload())
- assert.Empty(t, updateUser.FsConfig.AzBlobConfig.SASURL.GetKey())
- assert.Empty(t, updateUser.FsConfig.AzBlobConfig.SASURL.GetAdditionalData())
- // now check that a redacted sas url is not saved
- form.Set("az_sas_url", redactedSecret)
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- lastUpdatedUser = dataprovider.User{}
- err = render.DecodeJSON(rr.Body, &lastUpdatedUser)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.AzBlobConfig.SASURL.GetStatus())
- assert.Equal(t, updateUser.FsConfig.AzBlobConfig.SASURL.GetPayload(), lastUpdatedUser.FsConfig.AzBlobConfig.SASURL.GetPayload())
- assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.SASURL.GetKey())
- assert.Empty(t, lastUpdatedUser.FsConfig.AzBlobConfig.SASURL.GetAdditionalData())
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestWebUserCryptMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.CryptedFilesystemProvider
- user.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret("crypted passphrase")
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- form.Set("password", redactedSecret)
- form.Set("home_dir", user.HomeDir)
- form.Set("uid", "0")
- form.Set("gid", strconv.FormatInt(int64(user.GID), 10))
- form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10))
- form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10))
- form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("permissions", "*")
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "2020-01-01 00:00:00")
- form.Set("allowed_ip", "")
- form.Set("denied_ip", "")
- form.Set("fs_provider", "4")
- form.Set("crypt_passphrase", "")
- form.Set("pattern_path0", "/dir1")
- form.Set("patterns0", "*.jpg,*.png")
- form.Set("pattern_type0", "allowed")
- form.Set("pattern_path1", "/dir2")
- form.Set("patterns1", "*.zip")
- form.Set("pattern_type1", "denied")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- // passphrase cannot be empty
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("crypt_passphrase", user.FsConfig.CryptConfig.Passphrase.GetPayload())
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updateUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updateUser)
- assert.NoError(t, err)
- assert.Equal(t, int64(1577836800000), updateUser.ExpirationDate)
- assert.Equal(t, 2, len(updateUser.Filters.FilePatterns))
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateUser.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.NotEmpty(t, updateUser.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, updateUser.FsConfig.CryptConfig.Passphrase.GetKey())
- assert.Empty(t, updateUser.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- // now check that a redacted password is not saved
- form.Set("crypt_passphrase", redactedSecret+" ")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var lastUpdatedUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &lastUpdatedUser)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.CryptConfig.Passphrase.GetStatus())
- assert.Equal(t, updateUser.FsConfig.CryptConfig.Passphrase.GetPayload(), lastUpdatedUser.FsConfig.CryptConfig.Passphrase.GetPayload())
- assert.Empty(t, lastUpdatedUser.FsConfig.CryptConfig.Passphrase.GetKey())
- assert.Empty(t, lastUpdatedUser.FsConfig.CryptConfig.Passphrase.GetAdditionalData())
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestWebUserSFTPFsMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user := getTestUser()
- userAsJSON := getUserAsJSON(t, user)
- req, _ := http.NewRequest(http.MethodPost, userPath, bytes.NewBuffer(userAsJSON))
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- err = render.DecodeJSON(rr.Body, &user)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.SFTPFilesystemProvider
- user.FsConfig.SFTPConfig.Endpoint = "127.0.0.1:22"
- user.FsConfig.SFTPConfig.Username = "sftpuser"
- user.FsConfig.SFTPConfig.Password = kms.NewPlainSecret("pwd")
- user.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret(sftpPrivateKey)
- user.FsConfig.SFTPConfig.KeyPassphrase = kms.NewPlainSecret(testPrivateKeyPwd)
- user.FsConfig.SFTPConfig.Fingerprints = []string{sftpPkeyFingerprint}
- user.FsConfig.SFTPConfig.Prefix = "/home/sftpuser"
- user.FsConfig.SFTPConfig.DisableCouncurrentReads = true
- user.FsConfig.SFTPConfig.BufferSize = 5
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- form.Set("password", redactedSecret)
- form.Set("home_dir", user.HomeDir)
- form.Set("uid", "0")
- form.Set("gid", strconv.FormatInt(int64(user.GID), 10))
- form.Set("max_sessions", strconv.FormatInt(int64(user.MaxSessions), 10))
- form.Set("quota_size", strconv.FormatInt(user.QuotaSize, 10))
- form.Set("quota_files", strconv.FormatInt(int64(user.QuotaFiles), 10))
- form.Set("upload_bandwidth", "0")
- form.Set("download_bandwidth", "0")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("permissions", "*")
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("expiration_date", "2020-01-01 00:00:00")
- form.Set("allowed_ip", "")
- form.Set("denied_ip", "")
- form.Set("fs_provider", "5")
- form.Set("crypt_passphrase", "")
- form.Set("pattern_path0", "/dir1")
- form.Set("patterns0", "*.jpg,*.png")
- form.Set("pattern_type0", "allowed")
- form.Set("pattern_path1", "/dir2")
- form.Set("patterns1", "*.zip")
- form.Set("pattern_type1", "denied")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- // empty sftpconfig
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("sftp_endpoint", user.FsConfig.SFTPConfig.Endpoint)
- form.Set("sftp_username", user.FsConfig.SFTPConfig.Username)
- form.Set("sftp_password", user.FsConfig.SFTPConfig.Password.GetPayload())
- form.Set("sftp_private_key", user.FsConfig.SFTPConfig.PrivateKey.GetPayload())
- form.Set("sftp_key_passphrase", user.FsConfig.SFTPConfig.KeyPassphrase.GetPayload())
- form.Set("sftp_fingerprints", user.FsConfig.SFTPConfig.Fingerprints[0])
- form.Set("sftp_prefix", user.FsConfig.SFTPConfig.Prefix)
- form.Set("sftp_disable_concurrent_reads", "true")
- form.Set("sftp_equality_check_mode", "true")
- form.Set("sftp_buffer_size", strconv.FormatInt(user.FsConfig.SFTPConfig.BufferSize, 10))
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var updateUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &updateUser)
- assert.NoError(t, err)
- assert.Equal(t, int64(1577836800000), updateUser.ExpirationDate)
- assert.Equal(t, 2, len(updateUser.Filters.FilePatterns))
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateUser.FsConfig.SFTPConfig.Password.GetStatus())
- assert.NotEmpty(t, updateUser.FsConfig.SFTPConfig.Password.GetPayload())
- assert.Empty(t, updateUser.FsConfig.SFTPConfig.Password.GetKey())
- assert.Empty(t, updateUser.FsConfig.SFTPConfig.Password.GetAdditionalData())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateUser.FsConfig.SFTPConfig.PrivateKey.GetStatus())
- assert.NotEmpty(t, updateUser.FsConfig.SFTPConfig.PrivateKey.GetPayload())
- assert.Empty(t, updateUser.FsConfig.SFTPConfig.PrivateKey.GetKey())
- assert.Empty(t, updateUser.FsConfig.SFTPConfig.PrivateKey.GetAdditionalData())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateUser.FsConfig.SFTPConfig.KeyPassphrase.GetStatus())
- assert.NotEmpty(t, updateUser.FsConfig.SFTPConfig.KeyPassphrase.GetPayload())
- assert.Empty(t, updateUser.FsConfig.SFTPConfig.KeyPassphrase.GetKey())
- assert.Empty(t, updateUser.FsConfig.SFTPConfig.KeyPassphrase.GetAdditionalData())
- assert.Equal(t, updateUser.FsConfig.SFTPConfig.Prefix, user.FsConfig.SFTPConfig.Prefix)
- assert.Equal(t, updateUser.FsConfig.SFTPConfig.Username, user.FsConfig.SFTPConfig.Username)
- assert.Equal(t, updateUser.FsConfig.SFTPConfig.Endpoint, user.FsConfig.SFTPConfig.Endpoint)
- assert.True(t, updateUser.FsConfig.SFTPConfig.DisableCouncurrentReads)
- assert.Len(t, updateUser.FsConfig.SFTPConfig.Fingerprints, 1)
- assert.Equal(t, user.FsConfig.SFTPConfig.BufferSize, updateUser.FsConfig.SFTPConfig.BufferSize)
- assert.Contains(t, updateUser.FsConfig.SFTPConfig.Fingerprints, sftpPkeyFingerprint)
- assert.Equal(t, 1, updateUser.FsConfig.SFTPConfig.EqualityCheckMode)
- // now check that a redacted credentials are not saved
- form.Set("sftp_password", redactedSecret+" ")
- form.Set("sftp_private_key", redactedSecret)
- form.Set("sftp_key_passphrase", redactedSecret)
- form.Set("sftp_equality_check_mode", "")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var lastUpdatedUser dataprovider.User
- err = render.DecodeJSON(rr.Body, &lastUpdatedUser)
- assert.NoError(t, err)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.SFTPConfig.Password.GetStatus())
- assert.Equal(t, updateUser.FsConfig.SFTPConfig.Password.GetPayload(), lastUpdatedUser.FsConfig.SFTPConfig.Password.GetPayload())
- assert.Empty(t, lastUpdatedUser.FsConfig.SFTPConfig.Password.GetKey())
- assert.Empty(t, lastUpdatedUser.FsConfig.SFTPConfig.Password.GetAdditionalData())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.SFTPConfig.PrivateKey.GetStatus())
- assert.Equal(t, updateUser.FsConfig.SFTPConfig.PrivateKey.GetPayload(), lastUpdatedUser.FsConfig.SFTPConfig.PrivateKey.GetPayload())
- assert.Empty(t, lastUpdatedUser.FsConfig.SFTPConfig.PrivateKey.GetKey())
- assert.Empty(t, lastUpdatedUser.FsConfig.SFTPConfig.PrivateKey.GetAdditionalData())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, lastUpdatedUser.FsConfig.SFTPConfig.KeyPassphrase.GetStatus())
- assert.Equal(t, updateUser.FsConfig.SFTPConfig.KeyPassphrase.GetPayload(), lastUpdatedUser.FsConfig.SFTPConfig.KeyPassphrase.GetPayload())
- assert.Empty(t, lastUpdatedUser.FsConfig.SFTPConfig.KeyPassphrase.GetKey())
- assert.Empty(t, lastUpdatedUser.FsConfig.SFTPConfig.KeyPassphrase.GetAdditionalData())
- assert.Equal(t, 0, lastUpdatedUser.FsConfig.SFTPConfig.EqualityCheckMode)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestWebUserRole(t *testing.T) {
- role, resp, err := httpdtest.AddRole(getTestRole(), http.StatusCreated)
- assert.NoError(t, err, string(resp))
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- a.Role = role.Name
- a.Permissions = []string{dataprovider.PermAdminAddUsers, dataprovider.PermAdminChangeUsers,
- dataprovider.PermAdminDeleteUsers, dataprovider.PermAdminViewUsers}
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- webToken, err := getJWTWebTokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- user := getTestUser()
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- form.Set("home_dir", user.HomeDir)
- form.Set("password", user.Password)
- form.Set("status", strconv.Itoa(user.Status))
- form.Set("permissions", "*")
- form.Set("external_auth_cache_time", "0")
- form.Set("uid", "0")
- form.Set("gid", "0")
- form.Set("max_sessions", "0")
- form.Set("quota_size", "0")
- form.Set("quota_files", "0")
- form.Set("upload_bandwidth", strconv.FormatInt(user.UploadBandwidth, 10))
- form.Set("download_bandwidth", strconv.FormatInt(user.DownloadBandwidth, 10))
- form.Set("upload_data_transfer", strconv.FormatInt(user.UploadDataTransfer, 10))
- form.Set("download_data_transfer", strconv.FormatInt(user.DownloadDataTransfer, 10))
- form.Set("total_data_transfer", strconv.FormatInt(user.TotalDataTransfer, 10))
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "10")
- form.Set("password_expiration", "0")
- b, contentType, _ := getMultipartFormData(form, "", "")
- req, err := http.NewRequest(http.MethodPost, webUserPath, &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Name, user.Role)
- form.Set("role", "")
- b, contentType, _ = getMultipartFormData(form, "", "")
- req, _ = http.NewRequest(http.MethodPost, path.Join(webUserPath, user.Username), &b)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, role.Name, user.Role)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestWebEventAction(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- action := dataprovider.BaseEventAction{
- ID: 81,
- Name: "web_action_http",
- Description: "http web action",
- Type: dataprovider.ActionTypeHTTP,
- Options: dataprovider.BaseEventActionOptions{
- HTTPConfig: dataprovider.EventActionHTTPConfig{
- Endpoint: "https://localhost:4567/action",
- Username: defaultUsername,
- Headers: []dataprovider.KeyValue{
- {
- Key: "Content-Type",
- Value: "application/json",
- },
- },
- Password: kms.NewPlainSecret(defaultPassword),
- Timeout: 10,
- SkipTLSVerify: true,
- Method: http.MethodPost,
- QueryParameters: []dataprovider.KeyValue{
- {
- Key: "param1",
- Value: "value1",
- },
- },
- Body: `{"event":"{{Event}}","name":"{{Name}}"}`,
- },
- },
- }
- form := make(url.Values)
- form.Set("name", action.Name)
- form.Set("description", action.Description)
- form.Set("fs_action_type", "0")
- form.Set("type", "a")
- req, err := http.NewRequest(http.MethodPost, webAdminEventActionPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid action type")
- form.Set("type", fmt.Sprintf("%d", action.Type))
- form.Set("http_timeout", "b")
- req, err = http.NewRequest(http.MethodPost, webAdminEventActionPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid http timeout")
- form.Set("cmd_timeout", "20")
- form.Set("pwd_expiration_threshold", "10")
- form.Set("http_timeout", fmt.Sprintf("%d", action.Options.HTTPConfig.Timeout))
- form.Set("http_header_key0", action.Options.HTTPConfig.Headers[0].Key)
- form.Set("http_header_val0", action.Options.HTTPConfig.Headers[0].Value)
- form.Set("http_header_key1", action.Options.HTTPConfig.Headers[0].Key) // ignored
- form.Set("http_query_key0", action.Options.HTTPConfig.QueryParameters[0].Key)
- form.Set("http_query_val0", action.Options.HTTPConfig.QueryParameters[0].Value)
- form.Set("http_body", action.Options.HTTPConfig.Body)
- form.Set("http_skip_tls_verify", "1")
- form.Set("http_username", action.Options.HTTPConfig.Username)
- form.Set("http_password", action.Options.HTTPConfig.Password.GetPayload())
- form.Set("http_method", action.Options.HTTPConfig.Method)
- req, err = http.NewRequest(http.MethodPost, webAdminEventActionPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminEventActionPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "HTTP endpoint is required")
- form.Set("http_endpoint", action.Options.HTTPConfig.Endpoint)
- req, err = http.NewRequest(http.MethodPost, webAdminEventActionPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // a new add will fail
- req, err = http.NewRequest(http.MethodPost, webAdminEventActionPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // list actions
- req, err = http.NewRequest(http.MethodGet, webAdminEventActionsPath+"?qlimit=a", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // render add page
- req, err = http.NewRequest(http.MethodGet, webAdminEventActionPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // render action page
- req, err = http.NewRequest(http.MethodGet, path.Join(webAdminEventActionPath, action.Name), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // missing action
- req, err = http.NewRequest(http.MethodGet, path.Join(webAdminEventActionPath, action.Name+"1"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // check the action
- actionGet, _, err := httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, action.Type, actionGet.Type)
- assert.Equal(t, action.Description, actionGet.Description)
- assert.Equal(t, action.Options.HTTPConfig.Body, actionGet.Options.HTTPConfig.Body)
- assert.Equal(t, action.Options.HTTPConfig.Endpoint, actionGet.Options.HTTPConfig.Endpoint)
- assert.Equal(t, action.Options.HTTPConfig.Headers, actionGet.Options.HTTPConfig.Headers)
- assert.Equal(t, action.Options.HTTPConfig.Method, actionGet.Options.HTTPConfig.Method)
- assert.Equal(t, action.Options.HTTPConfig.SkipTLSVerify, actionGet.Options.HTTPConfig.SkipTLSVerify)
- assert.Equal(t, action.Options.HTTPConfig.Timeout, actionGet.Options.HTTPConfig.Timeout)
- assert.Equal(t, action.Options.HTTPConfig.Username, actionGet.Options.HTTPConfig.Username)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, actionGet.Options.HTTPConfig.Password.GetStatus())
- assert.NotEmpty(t, actionGet.Options.HTTPConfig.Password.GetPayload())
- assert.Empty(t, actionGet.Options.HTTPConfig.Password.GetKey())
- assert.Empty(t, actionGet.Options.HTTPConfig.Password.GetAdditionalData())
- // update and check that the password is preserved and the multipart fields
- form.Set("http_password", redactedSecret)
- form.Set("http_body", "")
- form.Set("http_timeout", "0")
- form.Del("http_header_key0")
- form.Del("http_header_val0")
- form.Set("http_part_name0", "part1")
- form.Set("http_part_file0", "{{VirtualPath}}")
- form.Set("http_part_headers0", "X-MyHeader: a:b,c")
- form.Set("http_part_body0", "")
- form.Set("http_part_namea", "ignored")
- form.Set("http_part_filea", "{{VirtualPath}}")
- form.Set("http_part_headersa", "X-MyHeader: a:b,c")
- form.Set("http_part_bodya", "")
- form.Set("http_part_name12", "part2")
- form.Set("http_part_headers12", "Content-Type:application/json \r\n")
- form.Set("http_part_body12", "{{ObjectData}}")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- dbAction, err := dataprovider.EventActionExists(action.Name)
- assert.NoError(t, err)
- err = dbAction.Options.HTTPConfig.Password.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, defaultPassword, dbAction.Options.HTTPConfig.Password.GetPayload())
- assert.Empty(t, dbAction.Options.HTTPConfig.Body)
- assert.Equal(t, 0, dbAction.Options.HTTPConfig.Timeout)
- if assert.Len(t, dbAction.Options.HTTPConfig.Parts, 2) {
- assert.Equal(t, "part1", dbAction.Options.HTTPConfig.Parts[0].Name)
- assert.Equal(t, "/{{VirtualPath}}", dbAction.Options.HTTPConfig.Parts[0].Filepath)
- assert.Empty(t, dbAction.Options.HTTPConfig.Parts[0].Body)
- assert.Equal(t, "X-MyHeader", dbAction.Options.HTTPConfig.Parts[0].Headers[0].Key)
- assert.Equal(t, "a:b,c", dbAction.Options.HTTPConfig.Parts[0].Headers[0].Value)
- assert.Equal(t, "part2", dbAction.Options.HTTPConfig.Parts[1].Name)
- assert.Equal(t, "{{ObjectData}}", dbAction.Options.HTTPConfig.Parts[1].Body)
- assert.Empty(t, dbAction.Options.HTTPConfig.Parts[1].Filepath)
- assert.Equal(t, "Content-Type", dbAction.Options.HTTPConfig.Parts[1].Headers[0].Key)
- assert.Equal(t, "application/json", dbAction.Options.HTTPConfig.Parts[1].Headers[0].Value)
- }
- // change action type
- action.Type = dataprovider.ActionTypeCommand
- action.Options.CmdConfig = dataprovider.EventActionCommandConfig{
- Cmd: filepath.Join(os.TempDir(), "cmd"),
- Args: []string{"arg1", "arg2"},
- Timeout: 20,
- EnvVars: []dataprovider.KeyValue{
- {
- Key: "key",
- Value: "val",
- },
- },
- }
- form.Set("type", fmt.Sprintf("%d", action.Type))
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "command is required")
- form.Set("cmd_path", action.Options.CmdConfig.Cmd)
- form.Set("cmd_timeout", "a")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid command timeout")
- form.Set("cmd_timeout", fmt.Sprintf("%d", action.Options.CmdConfig.Timeout))
- form.Set("cmd_env_key0", action.Options.CmdConfig.EnvVars[0].Key)
- form.Set("cmd_env_val0", action.Options.CmdConfig.EnvVars[0].Value)
- form.Set("cmd_arguments", "arg1 ,arg2 ")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // update a missing action
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name+"1"),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // update with no csrf token
- form.Del(csrfFormToken)
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- // check the update
- actionGet, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, action.Type, actionGet.Type)
- assert.Equal(t, action.Options.CmdConfig.Cmd, actionGet.Options.CmdConfig.Cmd)
- assert.Equal(t, action.Options.CmdConfig.Args, actionGet.Options.CmdConfig.Args)
- assert.Equal(t, action.Options.CmdConfig.Timeout, actionGet.Options.CmdConfig.Timeout)
- assert.Equal(t, action.Options.CmdConfig.EnvVars, actionGet.Options.CmdConfig.EnvVars)
- assert.Equal(t, dataprovider.EventActionHTTPConfig{}, actionGet.Options.HTTPConfig)
- assert.Equal(t, dataprovider.EventActionPasswordExpiration{}, actionGet.Options.PwdExpirationConfig)
- // change action type again
- action.Type = dataprovider.ActionTypeEmail
- action.Options.EmailConfig = dataprovider.EventActionEmailConfig{
- Recipients: []string{"address1@example.com", "address2@example.com"},
- Subject: "subject",
- Body: "body",
- Attachments: []string{"/file1.txt", "/file2.txt"},
- }
- form.Set("type", fmt.Sprintf("%d", action.Type))
- form.Set("email_recipients", "address1@example.com, address2@example.com")
- form.Set("email_subject", action.Options.EmailConfig.Subject)
- form.Set("email_body", action.Options.EmailConfig.Body)
- form.Set("email_attachments", "file1.txt, file2.txt")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // check the update
- actionGet, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, action.Type, actionGet.Type)
- assert.Equal(t, action.Options.EmailConfig.Recipients, actionGet.Options.EmailConfig.Recipients)
- assert.Equal(t, action.Options.EmailConfig.Subject, actionGet.Options.EmailConfig.Subject)
- assert.Equal(t, action.Options.EmailConfig.Body, actionGet.Options.EmailConfig.Body)
- assert.Equal(t, action.Options.EmailConfig.Attachments, actionGet.Options.EmailConfig.Attachments)
- assert.Equal(t, dataprovider.EventActionHTTPConfig{}, actionGet.Options.HTTPConfig)
- assert.Empty(t, actionGet.Options.CmdConfig.Cmd)
- assert.Equal(t, 0, actionGet.Options.CmdConfig.Timeout)
- assert.Len(t, actionGet.Options.CmdConfig.EnvVars, 0)
- // change action type to data retention check
- action.Type = dataprovider.ActionTypeDataRetentionCheck
- form.Set("type", fmt.Sprintf("%d", action.Type))
- form.Set("folder_retention_path10", "p1")
- form.Set("folder_retention_val10", "a")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid retention for path")
- form.Set("folder_retention_val10", "24")
- form.Set("folder_retention_options10", "1")
- form.Add("folder_retention_options10", "2")
- form.Set("folder_retention_path11", "../p2")
- form.Set("folder_retention_val11", "48")
- form.Set("folder_retention_options11", "1")
- form.Add("folder_retention_options12", "2") // ignored
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // check the update
- actionGet, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, action.Type, actionGet.Type)
- if assert.Len(t, actionGet.Options.RetentionConfig.Folders, 2) {
- for _, folder := range actionGet.Options.RetentionConfig.Folders {
- switch folder.Path {
- case "/p1":
- assert.Equal(t, 24, folder.Retention)
- assert.True(t, folder.DeleteEmptyDirs)
- assert.True(t, folder.IgnoreUserPermissions)
- case "/p2":
- assert.Equal(t, 48, folder.Retention)
- assert.True(t, folder.DeleteEmptyDirs)
- assert.False(t, folder.IgnoreUserPermissions)
- default:
- t.Errorf("unexpected folder path %v", folder.Path)
- }
- }
- }
- action.Type = dataprovider.ActionTypeFilesystem
- action.Options.FsConfig = dataprovider.EventActionFilesystemConfig{
- Type: dataprovider.FilesystemActionMkdirs,
- MkDirs: []string{"a ", " a/b"},
- }
- form.Set("type", fmt.Sprintf("%d", action.Type))
- form.Set("fs_mkdir_paths", strings.Join(action.Options.FsConfig.MkDirs, ","))
- form.Set("fs_action_type", "invalid")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid fs action type")
- form.Set("fs_action_type", fmt.Sprintf("%d", action.Options.FsConfig.Type))
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // check the update
- actionGet, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, action.Type, actionGet.Type)
- if assert.Len(t, actionGet.Options.FsConfig.MkDirs, 2) {
- for _, dir := range actionGet.Options.FsConfig.MkDirs {
- switch dir {
- case "/a":
- case "/a/b":
- default:
- t.Errorf("unexpected dir path %v", dir)
- }
- }
- }
- action.Options.FsConfig = dataprovider.EventActionFilesystemConfig{
- Type: dataprovider.FilesystemActionExist,
- Exist: []string{"b ", " c/d"},
- }
- form.Set("fs_action_type", fmt.Sprintf("%d", action.Options.FsConfig.Type))
- form.Set("fs_exist_paths", strings.Join(action.Options.FsConfig.Exist, ","))
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // check the update
- actionGet, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, action.Type, actionGet.Type)
- if assert.Len(t, actionGet.Options.FsConfig.Exist, 2) {
- for _, p := range actionGet.Options.FsConfig.Exist {
- switch p {
- case "/b":
- case "/c/d":
- default:
- t.Errorf("unexpected path %v", p)
- }
- }
- }
- action.Type = dataprovider.ActionTypePasswordExpirationCheck
- action.Options.PwdExpirationConfig.Threshold = 15
- form.Set("type", fmt.Sprintf("%d", action.Type))
- form.Set("pwd_expiration_threshold", "a")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid password expiration threshold")
- form.Set("pwd_expiration_threshold", strconv.Itoa(action.Options.PwdExpirationConfig.Threshold))
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventActionPath, action.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- actionGet, _, err = httpdtest.GetEventActionByName(action.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, action.Type, actionGet.Type)
- assert.Equal(t, action.Options.PwdExpirationConfig.Threshold, actionGet.Options.PwdExpirationConfig.Threshold)
- assert.Equal(t, 0, actionGet.Options.CmdConfig.Timeout)
- assert.Len(t, actionGet.Options.CmdConfig.EnvVars, 0)
- req, err = http.NewRequest(http.MethodDelete, path.Join(webAdminEventActionPath, action.Name), nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- req, err = http.NewRequest(http.MethodDelete, path.Join(webAdminEventActionPath, action.Name), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestWebEventRule(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- a := dataprovider.BaseEventAction{
- Name: "web_action",
- Type: dataprovider.ActionTypeFilesystem,
- Options: dataprovider.BaseEventActionOptions{
- FsConfig: dataprovider.EventActionFilesystemConfig{
- Type: dataprovider.FilesystemActionExist,
- Exist: []string{"/dir1"},
- },
- },
- }
- action, _, err := httpdtest.AddEventAction(a, http.StatusCreated)
- assert.NoError(t, err)
- rule := dataprovider.EventRule{
- Name: "test_web_rule",
- Description: "rule added using web API",
- Trigger: dataprovider.EventTriggerSchedule,
- Conditions: dataprovider.EventConditions{
- Schedules: []dataprovider.Schedule{
- {
- Hours: "0",
- DayOfWeek: "*",
- DayOfMonth: "*",
- Month: "*",
- },
- },
- Options: dataprovider.ConditionOptions{
- Names: []dataprovider.ConditionPattern{
- {
- Pattern: "u*",
- InverseMatch: true,
- },
- },
- GroupNames: []dataprovider.ConditionPattern{
- {
- Pattern: "g*",
- InverseMatch: true,
- },
- },
- RoleNames: []dataprovider.ConditionPattern{
- {
- Pattern: "g*",
- InverseMatch: true,
- },
- },
- },
- },
- Actions: []dataprovider.EventAction{
- {
- BaseEventAction: dataprovider.BaseEventAction{
- Name: action.Name,
- },
- Order: 1,
- },
- },
- }
- form := make(url.Values)
- form.Set("name", rule.Name)
- form.Set("description", rule.Description)
- form.Set("trigger", "a")
- req, err := http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid trigger")
- form.Set("trigger", fmt.Sprintf("%d", rule.Trigger))
- form.Set("schedule_hour0", rule.Conditions.Schedules[0].Hours)
- form.Set("schedule_day_of_week0", rule.Conditions.Schedules[0].DayOfWeek)
- form.Set("schedule_day_of_month0", rule.Conditions.Schedules[0].DayOfMonth)
- form.Set("schedule_month0", rule.Conditions.Schedules[0].Month)
- form.Set("name_pattern0", rule.Conditions.Options.Names[0].Pattern)
- form.Set("type_name_pattern0", "inverse")
- form.Set("group_name_pattern0", rule.Conditions.Options.GroupNames[0].Pattern)
- form.Set("type_group_name_pattern0", "inverse")
- form.Set("role_name_pattern0", rule.Conditions.Options.RoleNames[0].Pattern)
- form.Set("type_role_name_pattern0", "inverse")
- req, err = http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid min file size")
- form.Set("fs_min_size", "0")
- req, err = http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid max file size")
- form.Set("fs_max_size", "0")
- form.Set("action_name0", action.Name)
- form.Set("action_order0", "a")
- req, err = http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid order")
- form.Set("action_order0", "0")
- req, err = http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // a new add will fail
- req, err = http.NewRequest(http.MethodPost, webAdminEventRulePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // list rules
- req, err = http.NewRequest(http.MethodGet, webAdminEventRulesPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // render add page
- req, err = http.NewRequest(http.MethodGet, webAdminEventRulePath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // render rule page
- req, err = http.NewRequest(http.MethodGet, path.Join(webAdminEventRulePath, rule.Name), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // missing rule
- req, err = http.NewRequest(http.MethodGet, path.Join(webAdminEventRulePath, rule.Name+"1"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // check the rule
- ruleGet, _, err := httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, rule.Trigger, ruleGet.Trigger)
- assert.Equal(t, rule.Description, ruleGet.Description)
- assert.Equal(t, rule.Conditions, ruleGet.Conditions)
- if assert.Len(t, ruleGet.Actions, 1) {
- assert.Equal(t, rule.Actions[0].Name, ruleGet.Actions[0].Name)
- assert.Equal(t, rule.Actions[0].Order, ruleGet.Actions[0].Order)
- }
- // change rule trigger
- rule.Trigger = dataprovider.EventTriggerFsEvent
- rule.Conditions = dataprovider.EventConditions{
- FsEvents: []string{"upload", "download"},
- Options: dataprovider.ConditionOptions{
- Names: []dataprovider.ConditionPattern{
- {
- Pattern: "u*",
- InverseMatch: true,
- },
- },
- GroupNames: []dataprovider.ConditionPattern{
- {
- Pattern: "g*",
- InverseMatch: true,
- },
- },
- RoleNames: []dataprovider.ConditionPattern{
- {
- Pattern: "g*",
- InverseMatch: true,
- },
- },
- FsPaths: []dataprovider.ConditionPattern{
- {
- Pattern: "/subdir/*.txt",
- },
- },
- Protocols: []string{common.ProtocolSFTP, common.ProtocolHTTP},
- MinFileSize: 1024 * 1024,
- MaxFileSize: 5 * 1024 * 1024,
- },
- }
- form.Set("trigger", fmt.Sprintf("%d", rule.Trigger))
- for _, event := range rule.Conditions.FsEvents {
- form.Add("fs_events", event)
- }
- form.Set("fs_path_pattern0", rule.Conditions.Options.FsPaths[0].Pattern)
- for _, protocol := range rule.Conditions.Options.Protocols {
- form.Add("fs_protocols", protocol)
- }
- form.Set("fs_min_size", fmt.Sprintf("%d", rule.Conditions.Options.MinFileSize))
- form.Set("fs_max_size", fmt.Sprintf("%d", rule.Conditions.Options.MaxFileSize))
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventRulePath, rule.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // check the rule
- ruleGet, _, err = httpdtest.GetEventRuleByName(rule.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, rule.Trigger, ruleGet.Trigger)
- assert.Equal(t, rule.Description, ruleGet.Description)
- assert.Equal(t, rule.Conditions, ruleGet.Conditions)
- if assert.Len(t, ruleGet.Actions, 1) {
- assert.Equal(t, rule.Actions[0].Name, ruleGet.Actions[0].Name)
- assert.Equal(t, rule.Actions[0].Order, ruleGet.Actions[0].Order)
- }
- // update a missing rule
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventRulePath, rule.Name+"1"),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // update with no csrf token
- form.Del(csrfFormToken)
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventRulePath, rule.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- // update with no action defined
- form.Del("action_name0")
- form.Del("action_order0")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventRulePath, rule.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "at least one action is required")
- // invalid trigger
- form.Set("trigger", "a")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminEventRulePath, rule.Name),
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid trigger")
- req, err = http.NewRequest(http.MethodDelete, path.Join(webAdminEventRulePath, rule.Name), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodDelete, path.Join(webAdminEventActionPath, action.Name), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestWebRole(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- role := getTestRole()
- form := make(url.Values)
- form.Set("name", "")
- form.Set("description", role.Description)
- req, err := http.NewRequest(http.MethodPost, webAdminRolePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminRolePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "name is mandatory")
- form.Set("name", role.Name)
- req, err = http.NewRequest(http.MethodPost, webAdminRolePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // a new add will fail
- req, err = http.NewRequest(http.MethodPost, webAdminRolePath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // list roles
- req, err = http.NewRequest(http.MethodGet, webAdminRolesPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // render the new role page
- req, err = http.NewRequest(http.MethodGet, webAdminRolePath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webAdminRolePath, role.Name), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webAdminRolePath, "missing_role"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- // parse form error
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminRolePath, role.Name)+"?param=p%C4%AO%GH",
- bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid URL escape")
- // update role
- form.Set("description", "new desc")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminRolePath, role.Name), bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // check the changes
- role, _, err = httpdtest.GetRoleByName(role.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, "new desc", role.Description)
- // no CSRF token
- form.Set(csrfFormToken, "")
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminRolePath, role.Name), bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- // missing role
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminRolePath, "missing"), bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestAddWebGroup(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- group := getTestGroup()
- group.UserSettings = dataprovider.GroupUserSettings{
- BaseGroupUserSettings: sdk.BaseGroupUserSettings{
- HomeDir: filepath.Join(os.TempDir(), util.GenerateUniqueID()),
- Permissions: make(map[string][]string),
- MaxSessions: 2,
- QuotaSize: 123,
- QuotaFiles: 10,
- UploadBandwidth: 128,
- DownloadBandwidth: 256,
- },
- }
- form := make(url.Values)
- form.Set("name", group.Name)
- form.Set("description", group.Description)
- form.Set("home_dir", group.UserSettings.HomeDir)
- b, contentType, err := getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid max sessions")
- form.Set("max_sessions", strconv.FormatInt(int64(group.UserSettings.MaxSessions), 10))
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid quota size")
- form.Set("quota_files", strconv.FormatInt(int64(group.UserSettings.QuotaFiles), 10))
- form.Set("quota_size", strconv.FormatInt(group.UserSettings.QuotaSize, 10))
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid upload bandwidth")
- form.Set("upload_bandwidth", strconv.FormatInt(group.UserSettings.UploadBandwidth, 10))
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid download bandwidth")
- form.Set("download_bandwidth", strconv.FormatInt(group.UserSettings.DownloadBandwidth, 10))
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid upload data transfer")
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid max upload file size")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid external auth cache time")
- form.Set("external_auth_cache_time", "0")
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath+"?b=%2", &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr) // error parsing the multipart form
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // a new add will fail
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webGroupPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // list groups
- req, err = http.NewRequest(http.MethodGet, webGroupsPath, nil)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // render the new group page
- req, err = http.NewRequest(http.MethodGet, path.Join(webGroupPath, group.Name), nil)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // check the added group
- groupGet, _, err := httpdtest.GetGroupByName(group.Name, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, group.UserSettings, groupGet.UserSettings)
- assert.Equal(t, group.Name, groupGet.Name)
- assert.Equal(t, group.Description, groupGet.Description)
- // cleanup
- req, err = http.NewRequest(http.MethodDelete, path.Join(groupPath, group.Name), nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webGroupPath, group.Name), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestAddWebFoldersMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- mappedPath := filepath.Clean(os.TempDir())
- folderName := filepath.Base(mappedPath)
- folderDesc := "a simple desc"
- form := make(url.Values)
- form.Set("mapped_path", mappedPath)
- form.Set("name", folderName)
- form.Set("description", folderDesc)
- b, contentType, err := getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, webFolderPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webFolderPath, &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // adding the same folder will fail since the name must be unique
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webFolderPath, &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // invalid form
- req, err = http.NewRequest(http.MethodPost, webFolderPath, strings.NewReader(form.Encode()))
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", "text/plain; boundary=")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // now render the add folder page
- req, err = http.NewRequest(http.MethodGet, webFolderPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var folder vfs.BaseVirtualFolder
- req, _ = http.NewRequest(http.MethodGet, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &folder)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath, folder.MappedPath)
- assert.Equal(t, folderName, folder.Name)
- assert.Equal(t, folderDesc, folder.Description)
- // cleanup
- req, _ = http.NewRequest(http.MethodDelete, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestHTTPFsWebFolderMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- mappedPath := filepath.Clean(os.TempDir())
- folderName := filepath.Base(mappedPath)
- httpfsConfig := vfs.HTTPFsConfig{
- BaseHTTPFsConfig: sdk.BaseHTTPFsConfig{
- Endpoint: "https://127.0.0.1:9998/api/v1",
- Username: folderName,
- SkipTLSVerify: true,
- },
- Password: kms.NewPlainSecret(defaultPassword),
- APIKey: kms.NewPlainSecret(defaultTokenAuthPass),
- }
- form := make(url.Values)
- form.Set("mapped_path", mappedPath)
- form.Set("name", folderName)
- form.Set("fs_provider", "6")
- form.Set("http_endpoint", httpfsConfig.Endpoint)
- form.Set("http_username", "%name%")
- form.Set("http_password", httpfsConfig.Password.GetPayload())
- form.Set("http_api_key", httpfsConfig.APIKey.GetPayload())
- form.Set("http_skip_tls_verify", "checked")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, err := getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, webFolderPath, &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // check
- var folder vfs.BaseVirtualFolder
- req, _ = http.NewRequest(http.MethodGet, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &folder)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath, folder.MappedPath)
- assert.Equal(t, folderName, folder.Name)
- assert.Equal(t, sdk.HTTPFilesystemProvider, folder.FsConfig.Provider)
- assert.Equal(t, httpfsConfig.Endpoint, folder.FsConfig.HTTPConfig.Endpoint)
- assert.Equal(t, httpfsConfig.Username, folder.FsConfig.HTTPConfig.Username)
- assert.Equal(t, httpfsConfig.SkipTLSVerify, folder.FsConfig.HTTPConfig.SkipTLSVerify)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, folder.FsConfig.HTTPConfig.Password.GetStatus())
- assert.NotEmpty(t, folder.FsConfig.HTTPConfig.Password.GetPayload())
- assert.Empty(t, folder.FsConfig.HTTPConfig.Password.GetKey())
- assert.Empty(t, folder.FsConfig.HTTPConfig.Password.GetAdditionalData())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, folder.FsConfig.HTTPConfig.APIKey.GetStatus())
- assert.NotEmpty(t, folder.FsConfig.HTTPConfig.APIKey.GetPayload())
- assert.Empty(t, folder.FsConfig.HTTPConfig.APIKey.GetKey())
- assert.Empty(t, folder.FsConfig.HTTPConfig.APIKey.GetAdditionalData())
- // update
- form.Set("http_password", redactedSecret)
- form.Set("http_api_key", redactedSecret)
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webFolderPath, folderName), &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- // check
- var updateFolder vfs.BaseVirtualFolder
- req, _ = http.NewRequest(http.MethodGet, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &updateFolder)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath, updateFolder.MappedPath)
- assert.Equal(t, folderName, updateFolder.Name)
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateFolder.FsConfig.HTTPConfig.Password.GetStatus())
- assert.Equal(t, folder.FsConfig.HTTPConfig.Password.GetPayload(), updateFolder.FsConfig.HTTPConfig.Password.GetPayload())
- assert.Empty(t, updateFolder.FsConfig.HTTPConfig.Password.GetKey())
- assert.Empty(t, updateFolder.FsConfig.HTTPConfig.Password.GetAdditionalData())
- assert.Equal(t, sdkkms.SecretStatusSecretBox, updateFolder.FsConfig.HTTPConfig.APIKey.GetStatus())
- assert.Equal(t, folder.FsConfig.HTTPConfig.APIKey.GetPayload(), updateFolder.FsConfig.HTTPConfig.APIKey.GetPayload())
- assert.Empty(t, updateFolder.FsConfig.HTTPConfig.APIKey.GetKey())
- assert.Empty(t, updateFolder.FsConfig.HTTPConfig.APIKey.GetAdditionalData())
- // cleanup
- req, _ = http.NewRequest(http.MethodDelete, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestS3WebFolderMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- mappedPath := filepath.Clean(os.TempDir())
- folderName := filepath.Base(mappedPath)
- folderDesc := "a simple desc"
- S3Bucket := "test"
- S3Region := "eu-west-1"
- S3AccessKey := "access-key"
- S3AccessSecret := kms.NewPlainSecret("folder-access-secret")
- S3SessionToken := "fake session token"
- S3RoleARN := "arn:aws:iam::123456789012:user/Development/product_1234/*"
- S3Endpoint := "http://127.0.0.1:9000/path?b=c"
- S3StorageClass := "Standard"
- S3ACL := "public-read-write"
- S3KeyPrefix := "somedir/subdir/"
- S3UploadPartSize := 5
- S3UploadConcurrency := 4
- S3MaxPartDownloadTime := 120
- S3MaxPartUploadTime := 60
- S3DownloadPartSize := 6
- S3DownloadConcurrency := 3
- form := make(url.Values)
- form.Set("mapped_path", mappedPath)
- form.Set("name", folderName)
- form.Set("description", folderDesc)
- form.Set("fs_provider", "1")
- form.Set("s3_bucket", S3Bucket)
- form.Set("s3_region", S3Region)
- form.Set("s3_access_key", S3AccessKey)
- form.Set("s3_access_secret", S3AccessSecret.GetPayload())
- form.Set("s3_session_token", S3SessionToken)
- form.Set("s3_role_arn", S3RoleARN)
- form.Set("s3_storage_class", S3StorageClass)
- form.Set("s3_acl", S3ACL)
- form.Set("s3_endpoint", S3Endpoint)
- form.Set("s3_key_prefix", S3KeyPrefix)
- form.Set("s3_upload_part_size", strconv.Itoa(S3UploadPartSize))
- form.Set("s3_download_part_max_time", strconv.Itoa(S3MaxPartDownloadTime))
- form.Set("s3_download_part_size", strconv.Itoa(S3DownloadPartSize))
- form.Set("s3_download_concurrency", strconv.Itoa(S3DownloadConcurrency))
- form.Set("s3_upload_part_max_time", strconv.Itoa(S3MaxPartUploadTime))
- form.Set("s3_upload_concurrency", "a")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, err := getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, webFolderPath, &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("s3_upload_concurrency", strconv.Itoa(S3UploadConcurrency))
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, webFolderPath, &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- var folder vfs.BaseVirtualFolder
- req, _ = http.NewRequest(http.MethodGet, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &folder)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath, folder.MappedPath)
- assert.Equal(t, folderName, folder.Name)
- assert.Equal(t, folderDesc, folder.Description)
- assert.Equal(t, sdk.S3FilesystemProvider, folder.FsConfig.Provider)
- assert.Equal(t, S3Bucket, folder.FsConfig.S3Config.Bucket)
- assert.Equal(t, S3Region, folder.FsConfig.S3Config.Region)
- assert.Equal(t, S3AccessKey, folder.FsConfig.S3Config.AccessKey)
- assert.NotEmpty(t, folder.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Equal(t, S3Endpoint, folder.FsConfig.S3Config.Endpoint)
- assert.Equal(t, S3StorageClass, folder.FsConfig.S3Config.StorageClass)
- assert.Equal(t, S3ACL, folder.FsConfig.S3Config.ACL)
- assert.Equal(t, S3KeyPrefix, folder.FsConfig.S3Config.KeyPrefix)
- assert.Equal(t, S3UploadConcurrency, folder.FsConfig.S3Config.UploadConcurrency)
- assert.Equal(t, int64(S3UploadPartSize), folder.FsConfig.S3Config.UploadPartSize)
- assert.Equal(t, S3MaxPartDownloadTime, folder.FsConfig.S3Config.DownloadPartMaxTime)
- assert.Equal(t, S3MaxPartUploadTime, folder.FsConfig.S3Config.UploadPartMaxTime)
- assert.Equal(t, S3DownloadConcurrency, folder.FsConfig.S3Config.DownloadConcurrency)
- assert.Equal(t, int64(S3DownloadPartSize), folder.FsConfig.S3Config.DownloadPartSize)
- assert.False(t, folder.FsConfig.S3Config.ForcePathStyle)
- // update
- S3UploadConcurrency = 10
- form.Set("s3_upload_concurrency", "b")
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webFolderPath, folderName), &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form.Set("s3_upload_concurrency", strconv.Itoa(S3UploadConcurrency))
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webFolderPath, folderName), &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- folder = vfs.BaseVirtualFolder{}
- req, _ = http.NewRequest(http.MethodGet, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &folder)
- assert.NoError(t, err)
- assert.Equal(t, mappedPath, folder.MappedPath)
- assert.Equal(t, folderName, folder.Name)
- assert.Equal(t, folderDesc, folder.Description)
- assert.Equal(t, sdk.S3FilesystemProvider, folder.FsConfig.Provider)
- assert.Equal(t, S3Bucket, folder.FsConfig.S3Config.Bucket)
- assert.Equal(t, S3Region, folder.FsConfig.S3Config.Region)
- assert.Equal(t, S3AccessKey, folder.FsConfig.S3Config.AccessKey)
- assert.Equal(t, S3RoleARN, folder.FsConfig.S3Config.RoleARN)
- assert.NotEmpty(t, folder.FsConfig.S3Config.AccessSecret.GetPayload())
- assert.Equal(t, S3Endpoint, folder.FsConfig.S3Config.Endpoint)
- assert.Equal(t, S3StorageClass, folder.FsConfig.S3Config.StorageClass)
- assert.Equal(t, S3KeyPrefix, folder.FsConfig.S3Config.KeyPrefix)
- assert.Equal(t, S3UploadConcurrency, folder.FsConfig.S3Config.UploadConcurrency)
- assert.Equal(t, int64(S3UploadPartSize), folder.FsConfig.S3Config.UploadPartSize)
- // cleanup
- req, _ = http.NewRequest(http.MethodDelete, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestUpdateWebGroupMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- group, _, err := httpdtest.AddGroup(getTestGroup(), http.StatusCreated)
- assert.NoError(t, err)
- group.UserSettings = dataprovider.GroupUserSettings{
- BaseGroupUserSettings: sdk.BaseGroupUserSettings{
- HomeDir: filepath.Join(os.TempDir(), util.GenerateUniqueID()),
- Permissions: make(map[string][]string),
- },
- FsConfig: vfs.Filesystem{
- Provider: sdk.SFTPFilesystemProvider,
- SFTPConfig: vfs.SFTPFsConfig{
- BaseSFTPFsConfig: sdk.BaseSFTPFsConfig{
- Endpoint: sftpServerAddr,
- Username: defaultUsername,
- BufferSize: 1,
- },
- },
- },
- }
- form := make(url.Values)
- form.Set("name", group.Name)
- form.Set("description", group.Description)
- form.Set("home_dir", group.UserSettings.HomeDir)
- form.Set("max_sessions", strconv.FormatInt(int64(group.UserSettings.MaxSessions), 10))
- form.Set("quota_files", strconv.FormatInt(int64(group.UserSettings.QuotaFiles), 10))
- form.Set("quota_size", strconv.FormatInt(group.UserSettings.QuotaSize, 10))
- form.Set("upload_bandwidth", strconv.FormatInt(group.UserSettings.UploadBandwidth, 10))
- form.Set("download_bandwidth", strconv.FormatInt(group.UserSettings.DownloadBandwidth, 10))
- form.Set("upload_data_transfer", "0")
- form.Set("download_data_transfer", "0")
- form.Set("total_data_transfer", "0")
- form.Set("max_upload_file_size", "0")
- form.Set("default_shares_expiration", "0")
- form.Set("password_expiration", "0")
- form.Set("external_auth_cache_time", "0")
- form.Set("fs_provider", strconv.FormatInt(int64(group.UserSettings.FsConfig.Provider), 10))
- form.Set("sftp_endpoint", group.UserSettings.FsConfig.SFTPConfig.Endpoint)
- form.Set("sftp_username", group.UserSettings.FsConfig.SFTPConfig.Username)
- b, contentType, err := getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, path.Join(webGroupPath, group.Name), &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid SFTP buffer size")
- form.Set("sftp_buffer_size", strconv.FormatInt(group.UserSettings.FsConfig.SFTPConfig.BufferSize, 10))
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webGroupPath, group.Name), &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webGroupPath, group.Name), &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "credentials cannot be empty")
- form.Set("sftp_password", defaultPassword)
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webGroupPath, group.Name), &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, err = http.NewRequest(http.MethodDelete, path.Join(groupPath, group.Name), nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webGroupPath, group.Name), &b)
- assert.NoError(t, err)
- req.Header.Set("Content-Type", contentType)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestUpdateWebFolderMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- folderName := "vfolderupdate"
- folderDesc := "updated desc"
- folder := vfs.BaseVirtualFolder{
- Name: folderName,
- MappedPath: filepath.Join(os.TempDir(), "folderupdate"),
- Description: "dsc",
- }
- _, _, err = httpdtest.AddFolder(folder, http.StatusCreated)
- newMappedPath := folder.MappedPath + "1"
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("mapped_path", newMappedPath)
- form.Set("name", folderName)
- form.Set("description", folderDesc)
- form.Set(csrfFormToken, "")
- b, contentType, err := getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, path.Join(webFolderPath, folderName), &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "unable to verify form token")
- form.Set(csrfFormToken, csrfToken)
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webFolderPath, folderName), &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusSeeOther, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(folderPath, folderName), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err = render.DecodeJSON(rr.Body, &folder)
- assert.NoError(t, err)
- assert.Equal(t, newMappedPath, folder.MappedPath)
- assert.Equal(t, folderName, folder.Name)
- assert.Equal(t, folderDesc, folder.Description)
- // parse form error
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webFolderPath, folderName)+"??a=a%B3%A2%G3", &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Contains(t, rr.Body.String(), "invalid URL escape")
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webFolderPath, folderName+"1"), &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- form.Set("mapped_path", "arelative/path")
- b, contentType, err = getMultipartFormData(form, "", "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(webFolderPath, folderName), &b)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- req.Header.Set("Content-Type", contentType)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // render update folder page
- req, err = http.NewRequest(http.MethodGet, path.Join(webFolderPath, folderName), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webFolderPath, folderName+"1"), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webFolderPath, folderName), nil)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Invalid token")
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webFolderPath, folderName), nil)
- setJWTCookieForReq(req, apiToken) // api token is not accepted
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusFound, rr)
- assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webFolderPath, folderName), nil)
- setJWTCookieForReq(req, webToken)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestWebFoldersMock(t *testing.T) {
- webToken, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- apiToken, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- mappedPath1 := filepath.Join(os.TempDir(), "vfolder1")
- mappedPath2 := filepath.Join(os.TempDir(), "vfolder2")
- folderName1 := filepath.Base(mappedPath1)
- folderName2 := filepath.Base(mappedPath2)
- folderDesc1 := "vfolder1 desc"
- folderDesc2 := "vfolder2 desc"
- folders := []vfs.BaseVirtualFolder{
- {
- Name: folderName1,
- MappedPath: mappedPath1,
- Description: folderDesc1,
- },
- {
- Name: folderName2,
- MappedPath: mappedPath2,
- Description: folderDesc2,
- },
- }
- for _, folder := range folders {
- folderAsJSON, err := json.Marshal(folder)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodPost, folderPath, bytes.NewBuffer(folderAsJSON))
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, rr)
- }
- req, err := http.NewRequest(http.MethodGet, folderPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var foldersGet []vfs.BaseVirtualFolder
- err = render.DecodeJSON(rr.Body, &foldersGet)
- assert.NoError(t, err)
- numFound := 0
- for _, f := range foldersGet {
- if f.Name == folderName1 {
- assert.Equal(t, mappedPath1, f.MappedPath)
- assert.Equal(t, folderDesc1, f.Description)
- numFound++
- }
- if f.Name == folderName2 {
- assert.Equal(t, mappedPath2, f.MappedPath)
- assert.Equal(t, folderDesc2, f.Description)
- numFound++
- }
- }
- assert.Equal(t, 2, numFound)
- req, err = http.NewRequest(http.MethodGet, webFoldersPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webFoldersPath+"?qlimit=a", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webFoldersPath+"?qlimit=1", nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, webToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- for _, folder := range folders {
- req, _ := http.NewRequest(http.MethodDelete, path.Join(folderPath, folder.Name), nil)
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- }
- func TestAdminForgotPassword(t *testing.T) {
- smtpCfg := smtp.Config{
- Host: "127.0.0.1",
- Port: 3525,
- TemplatesPath: "templates",
- }
- err := smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webAdminForgotPwdPath, nil)
- assert.NoError(t, err)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminResetPwdPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webLoginPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form := make(url.Values)
- form.Set("username", "")
- // no csrf token
- req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusForbidden, rr.Code)
- // empty username
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "username is mandatory")
- lastResetCode = ""
- form.Set("username", altAdminUsername)
- req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- form = make(url.Values)
- req, err = http.NewRequest(http.MethodPost, webAdminResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusForbidden, rr.Code)
- // no password
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "please set a password")
- // no code
- form.Set("password", defaultPassword)
- req, err = http.NewRequest(http.MethodPost, webAdminResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "please set a confirmation code")
- // ok
- form.Set("code", lastResetCode)
- req, err = http.NewRequest(http.MethodPost, webAdminResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- form.Set("username", altAdminUsername)
- req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- // not working smtp server
- smtpCfg = smtp.Config{
- Host: "127.0.0.1",
- Port: 3526,
- TemplatesPath: "templates",
- }
- err = smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- form = make(url.Values)
- form.Set("username", altAdminUsername)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Unable to send confirmation code via email")
- smtpCfg = smtp.Config{}
- err = smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- form.Set("username", altAdminUsername)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webAdminForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Unable to render password reset template")
- req, err = http.NewRequest(http.MethodGet, webAdminForgotPwdPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminResetPwdPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- }
- func TestUserForgotPassword(t *testing.T) {
- smtpCfg := smtp.Config{
- Host: "127.0.0.1",
- Port: 3525,
- TemplatesPath: "templates",
- }
- err := smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- u := getTestUser()
- u.Email = "user@test.com"
- u.Filters.WebClient = []string{sdk.WebClientPasswordResetDisabled}
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, webClientForgotPwdPath, nil)
- assert.NoError(t, err)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientResetPwdPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, webClientLoginPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- form := make(url.Values)
- form.Set("username", "")
- // no csrf token
- req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusForbidden, rr.Code)
- // empty username
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "username is mandatory")
- // user cannot reset the password
- form.Set("username", user.Username)
- req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "you are not allowed to reset your password")
- user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled}
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- lastResetCode = ""
- req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- // no csrf token
- form = make(url.Values)
- req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusForbidden, rr.Code)
- // no password
- form.Set(csrfFormToken, csrfToken)
- req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "please set a password")
- // no code
- form.Set("password", altAdminPassword)
- req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "please set a confirmation code")
- // ok
- form.Set("code", lastResetCode)
- req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", user.Username)
- lastResetCode = ""
- req, err = http.NewRequest(http.MethodPost, webClientForgotPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusFound, rr.Code)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- smtpCfg = smtp.Config{}
- err = smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, webClientForgotPwdPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- req, err = http.NewRequest(http.MethodGet, webClientResetPwdPath, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- // user does not exist anymore
- form = make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("code", lastResetCode)
- form.Set("password", "pwd")
- req, err = http.NewRequest(http.MethodPost, webClientResetPwdPath, bytes.NewBuffer([]byte(form.Encode())))
- assert.NoError(t, err)
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr = executeRequest(req)
- assert.Equal(t, http.StatusOK, rr.Code)
- assert.Contains(t, rr.Body.String(), "Unable to associate the confirmation code with an existing user")
- }
- func TestAPIForgotPassword(t *testing.T) {
- smtpCfg := smtp.Config{
- Host: "127.0.0.1",
- Port: 3525,
- TemplatesPath: "templates",
- }
- err := smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- a.Email = ""
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- // no email, forgot pwd will not work
- lastResetCode = ""
- req, err := http.NewRequest(http.MethodPost, path.Join(adminPath, altAdminUsername, "/forgot-password"), nil)
- assert.NoError(t, err)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Your account does not have an email address")
- admin.Email = "admin@test.com"
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, altAdminUsername, "/forgot-password"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- // invalid JSON
- req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, altAdminUsername, "/reset-password"), bytes.NewBuffer([]byte(`{`)))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- resetReq := make(map[string]string)
- resetReq["code"] = lastResetCode
- resetReq["password"] = defaultPassword
- asJSON, err := json.Marshal(resetReq)
- assert.NoError(t, err)
- // a user cannot use an admin code
- req, err = http.NewRequest(http.MethodPost, path.Join(userPath, defaultUsername, "/reset-password"), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "invalid confirmation code")
- req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, altAdminUsername, "/reset-password"), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // the same code cannot be reused
- req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, altAdminUsername, "/reset-password"), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "confirmation code not found")
- admin, err = dataprovider.AdminExists(altAdminUsername)
- assert.NoError(t, err)
- match, err := admin.CheckPassword(defaultPassword)
- assert.NoError(t, err)
- assert.True(t, match)
- lastResetCode = ""
- // now the same for a user
- u := getTestUser()
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(userPath, defaultUsername, "/forgot-password"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "Your account does not have an email address")
- user.Email = "user@test.com"
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(userPath, defaultUsername, "/forgot-password"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- // invalid JSON
- req, err = http.NewRequest(http.MethodPost, path.Join(userPath, defaultUsername, "/reset-password"), bytes.NewBuffer([]byte(`{`)))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- // remove the reset password permission
- user.Filters.WebClient = []string{sdk.WebClientPasswordResetDisabled}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- resetReq["code"] = lastResetCode
- resetReq["password"] = altAdminPassword
- asJSON, err = json.Marshal(resetReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(userPath, defaultUsername, "/reset-password"), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "you are not allowed to reset your password")
- user.Filters.WebClient = []string{sdk.WebClientSharesDisabled}
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(userPath, defaultUsername, "/reset-password"), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- // the same code cannot be reused
- req, err = http.NewRequest(http.MethodPost, path.Join(userPath, defaultUsername, "/reset-password"), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "confirmation code not found")
- user, err = dataprovider.UserExists(defaultUsername, "")
- assert.NoError(t, err)
- err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(altAdminPassword))
- assert.NoError(t, err)
- lastResetCode = ""
- // a request for a missing admin/user will be silently ignored
- req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, "missing-admin", "/forgot-password"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Empty(t, lastResetCode)
- req, err = http.NewRequest(http.MethodPost, path.Join(userPath, "missing-user", "/forgot-password"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Empty(t, lastResetCode)
- lastResetCode = ""
- req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, altAdminUsername, "/forgot-password"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.GreaterOrEqual(t, len(lastResetCode), 20)
- smtpCfg = smtp.Config{}
- err = smtpCfg.Initialize(configDir)
- require.NoError(t, err)
- // without an smtp configuration reset password is not available
- req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, altAdminUsername, "/forgot-password"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "No SMTP configuration")
- req, err = http.NewRequest(http.MethodPost, path.Join(userPath, defaultUsername, "/forgot-password"), nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "No SMTP configuration")
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- // the admin does not exist anymore
- resetReq["code"] = lastResetCode
- resetReq["password"] = altAdminPassword
- asJSON, err = json.Marshal(resetReq)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPost, path.Join(adminPath, altAdminUsername, "/reset-password"), bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "unable to associate the confirmation code with an existing admin")
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- err = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- func TestProviderClosedMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- // create a role admin
- role, resp, err := httpdtest.AddRole(getTestRole(), http.StatusCreated)
- assert.NoError(t, err, string(resp))
- a := getTestAdmin()
- a.Username = altAdminUsername
- a.Password = altAdminPassword
- a.Role = role.Name
- a.Permissions = []string{dataprovider.PermAdminAddUsers, dataprovider.PermAdminChangeUsers,
- dataprovider.PermAdminDeleteUsers, dataprovider.PermAdminViewUsers}
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- altToken, err := getJWTWebTokenFromTestServer(altAdminUsername, altAdminPassword)
- assert.NoError(t, err)
- dataprovider.Close()
- req, _ := http.NewRequest(http.MethodGet, webFoldersPath, nil)
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodGet, webUsersPath, nil)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodGet, webUserPath+"/0", nil)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- form := make(url.Values)
- form.Set(csrfFormToken, csrfToken)
- form.Set("username", "test")
- req, _ = http.NewRequest(http.MethodPost, webUserPath+"/0", strings.NewReader(form.Encode()))
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(webAdminPath, defaultTokenAuthUser), nil)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodPost, path.Join(webAdminPath, defaultTokenAuthUser), strings.NewReader(form.Encode()))
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodGet, webAdminsPath, nil)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodGet, path.Join(webFolderPath, defaultTokenAuthUser), nil)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodPost, path.Join(webFolderPath, defaultTokenAuthUser), strings.NewReader(form.Encode()))
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodPost, webUserPath, strings.NewReader(form.Encode()))
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, _ = http.NewRequest(http.MethodPost, webUserPath, strings.NewReader(form.Encode()))
- setJWTCookieForReq(req, altToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, webAdminRolesPath, nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, path.Join(webAdminRolePath, role.Name), nil)
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodPost, path.Join(webAdminRolePath, role.Name), strings.NewReader(form.Encode()))
- assert.NoError(t, err)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- if config.GetProviderConf().Driver != dataprovider.MemoryDataProviderName {
- _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveRole(role, http.StatusOK)
- assert.NoError(t, err)
- }
- }
- func TestWebConnectionsMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, webConnectionsPath, nil)
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webConnectionsPath, "id"), nil)
- setJWTCookieForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Invalid token")
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webConnectionsPath, "id"), nil)
- setJWTCookieForReq(req, token)
- setCSRFHeaderForReq(req, "csrfToken")
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- assert.Contains(t, rr.Body.String(), "Invalid token")
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodDelete, path.Join(webConnectionsPath, "id"), nil)
- setJWTCookieForReq(req, token)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusNotFound, rr)
- }
- func TestGetWebStatusMock(t *testing.T) {
- token, err := getJWTWebTokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodGet, webStatusPath, nil)
- setJWTCookieForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestStaticFilesMock(t *testing.T) {
- req, err := http.NewRequest(http.MethodGet, "/static/favicon.ico", nil)
- assert.NoError(t, err)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, "/openapi/openapi.yaml", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, "/static", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusMovedPermanently, rr)
- location := rr.Header().Get("Location")
- assert.Equal(t, "/static/", location)
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, "/openapi", nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusMovedPermanently, rr)
- location = rr.Header().Get("Location")
- assert.Equal(t, "/openapi/", location)
- req, err = http.NewRequest(http.MethodGet, location, nil)
- assert.NoError(t, err)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- }
- func TestPasswordChangeRequired(t *testing.T) {
- user := getTestUser()
- assert.False(t, user.MustChangePassword())
- user.Filters.RequirePasswordChange = true
- assert.True(t, user.MustChangePassword())
- user.Filters.RequirePasswordChange = false
- assert.False(t, user.MustChangePassword())
- user.Filters.PasswordExpiration = 2
- user.LastPasswordChange = util.GetTimeAsMsSinceEpoch(time.Now())
- assert.False(t, user.MustChangePassword())
- user.LastPasswordChange = util.GetTimeAsMsSinceEpoch(time.Now().Add(49 * time.Hour))
- assert.False(t, user.MustChangePassword())
- user.LastPasswordChange = util.GetTimeAsMsSinceEpoch(time.Now().Add(-49 * time.Hour))
- assert.True(t, user.MustChangePassword())
- }
- func TestPasswordExpiresIn(t *testing.T) {
- user := getTestUser()
- user.Filters.PasswordExpiration = 30
- user.LastPasswordChange = util.GetTimeAsMsSinceEpoch(time.Now().Add(-15*24*time.Hour + 1*time.Hour))
- res := user.PasswordExpiresIn()
- assert.Equal(t, 15, res)
- user.Filters.PasswordExpiration = 15
- res = user.PasswordExpiresIn()
- assert.Equal(t, 1, res)
- user.LastPasswordChange = util.GetTimeAsMsSinceEpoch(time.Now().Add(-15*24*time.Hour - 1*time.Hour))
- res = user.PasswordExpiresIn()
- assert.Equal(t, 0, res)
- user.Filters.PasswordExpiration = 5
- res = user.PasswordExpiresIn()
- assert.Equal(t, -10, res)
- }
- func TestSecondFactorRequirements(t *testing.T) {
- user := getTestUser()
- user.Filters.TwoFactorAuthProtocols = []string{common.ProtocolHTTP, common.ProtocolSSH}
- assert.True(t, user.MustSetSecondFactor())
- assert.False(t, user.MustSetSecondFactorForProtocol(common.ProtocolFTP))
- assert.True(t, user.MustSetSecondFactorForProtocol(common.ProtocolHTTP))
- assert.True(t, user.MustSetSecondFactorForProtocol(common.ProtocolSSH))
- user.Filters.TOTPConfig.Enabled = true
- assert.True(t, user.MustSetSecondFactor())
- assert.False(t, user.MustSetSecondFactorForProtocol(common.ProtocolFTP))
- assert.True(t, user.MustSetSecondFactorForProtocol(common.ProtocolHTTP))
- assert.True(t, user.MustSetSecondFactorForProtocol(common.ProtocolSSH))
- user.Filters.TOTPConfig.Protocols = []string{common.ProtocolHTTP}
- assert.True(t, user.MustSetSecondFactor())
- assert.False(t, user.MustSetSecondFactorForProtocol(common.ProtocolFTP))
- assert.False(t, user.MustSetSecondFactorForProtocol(common.ProtocolHTTP))
- assert.True(t, user.MustSetSecondFactorForProtocol(common.ProtocolSSH))
- user.Filters.TOTPConfig.Protocols = []string{common.ProtocolHTTP, common.ProtocolSSH}
- assert.False(t, user.MustSetSecondFactor())
- assert.False(t, user.MustSetSecondFactorForProtocol(common.ProtocolFTP))
- assert.False(t, user.MustSetSecondFactorForProtocol(common.ProtocolHTTP))
- assert.False(t, user.MustSetSecondFactorForProtocol(common.ProtocolSSH))
- }
- func startOIDCMockServer() {
- go func() {
- http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
- fmt.Fprintf(w, "OK\n")
- })
- http.HandleFunc("/auth/realms/sftpgo/.well-known/openid-configuration", func(w http.ResponseWriter, _ *http.Request) {
- fmt.Fprintf(w, `{"issuer":"http://127.0.0.1:11111/auth/realms/sftpgo","authorization_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/auth","token_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/token","introspection_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/token/introspect","userinfo_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/userinfo","end_session_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/certs","check_session_iframe":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:ietf:params:oauth:grant-type:device_code","urn:openid:params:grant-type:ciba"],"response_types_supported":["code","none","id_token","token","id_token token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","phone","email","web-origins","offline_access","microprofile-jwt","profile","address","roles"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/token","revocation_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/revoke","introspection_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/token/introspect","device_authorization_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/auth/device","registration_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/clients-registrations/openid-connect","userinfo_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"http://127.0.0.1:11111/auth/realms/sftpgo/protocol/openid-connect/ext/ciba/auth"}}`)
- })
- http.HandleFunc("/404", func(w http.ResponseWriter, _ *http.Request) {
- w.WriteHeader(http.StatusNotFound)
- fmt.Fprintf(w, "Not found\n")
- })
- if err := http.ListenAndServe(oidcMockAddr, nil); err != nil {
- logger.ErrorToConsole("could not start HTTP notification server: %v", err)
- os.Exit(1)
- }
- }()
- waitTCPListening(oidcMockAddr)
- }
- func waitForUsersQuotaScan(t *testing.T, token string) {
- for {
- var scans []common.ActiveQuotaScan
- req, _ := http.NewRequest(http.MethodGet, quotaScanPath, nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err := render.DecodeJSON(rr.Body, &scans)
- if !assert.NoError(t, err, "Error getting active scans") {
- break
- }
- if len(scans) == 0 {
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
- }
- func waitForFoldersQuotaScanPath(t *testing.T, token string) {
- var scans []common.ActiveVirtualFolderQuotaScan
- for {
- req, _ := http.NewRequest(http.MethodGet, quotaScanVFolderPath, nil)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- err := render.DecodeJSON(rr.Body, &scans)
- if !assert.NoError(t, err, "Error getting active folders scans") {
- break
- }
- if len(scans) == 0 {
- break
- }
- time.Sleep(100 * time.Millisecond)
- }
- }
- func waitTCPListening(address string) {
- for {
- conn, err := net.Dial("tcp", address)
- if err != nil {
- logger.WarnToConsole("tcp server %v not listening: %v", address, err)
- time.Sleep(100 * time.Millisecond)
- continue
- }
- logger.InfoToConsole("tcp server %v now listening", address)
- conn.Close()
- break
- }
- }
- func startSMTPServer() {
- go func() {
- if err := smtpd.ListenAndServe(smtpServerAddr, func(_ net.Addr, _ string, _ []string, data []byte) error {
- re := regexp.MustCompile(`code is ".*?"`)
- code := strings.TrimPrefix(string(re.Find(data)), "code is ")
- lastResetCode = strings.ReplaceAll(code, "\"", "")
- return nil
- }, "SFTPGo test", "localhost"); err != nil {
- logger.ErrorToConsole("could not start SMTP server: %v", err)
- os.Exit(1)
- }
- }()
- waitTCPListening(smtpServerAddr)
- }
- func getTestAdmin() dataprovider.Admin {
- return dataprovider.Admin{
- Username: defaultTokenAuthUser,
- Password: defaultTokenAuthPass,
- Status: 1,
- Permissions: []string{dataprovider.PermAdminAny},
- Email: "admin@example.com",
- Description: "test admin",
- }
- }
- func getTestGroup() dataprovider.Group {
- return dataprovider.Group{
- BaseGroup: sdk.BaseGroup{
- Name: "test_group",
- Description: "test group description",
- },
- }
- }
- func getTestRole() dataprovider.Role {
- return dataprovider.Role{
- Name: "test_role",
- Description: "test role description",
- }
- }
- func getTestUser() dataprovider.User {
- user := dataprovider.User{
- BaseUser: sdk.BaseUser{
- Username: defaultUsername,
- Password: defaultPassword,
- HomeDir: filepath.Join(homeBasePath, defaultUsername),
- Status: 1,
- Description: "test user",
- },
- }
- user.Permissions = make(map[string][]string)
- user.Permissions["/"] = defaultPerms
- return user
- }
- func getTestSFTPUser() dataprovider.User {
- u := getTestUser()
- u.Username = u.Username + "_sftp"
- u.FsConfig.Provider = sdk.SFTPFilesystemProvider
- u.FsConfig.SFTPConfig.Endpoint = sftpServerAddr
- u.FsConfig.SFTPConfig.Username = defaultUsername
- u.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(defaultPassword)
- return u
- }
- func getUserAsJSON(t *testing.T, user dataprovider.User) []byte {
- json, err := json.Marshal(user)
- assert.NoError(t, err)
- return json
- }
- func getCSRFTokenMock(loginURLPath, remoteAddr string) (string, error) {
- req, err := http.NewRequest(http.MethodGet, loginURLPath, nil)
- if err != nil {
- return "", err
- }
- req.RemoteAddr = remoteAddr
- rr := executeRequest(req)
- return getCSRFTokenFromBody(bytes.NewBuffer(rr.Body.Bytes()))
- }
- func getCSRFToken(url string) (string, error) {
- req, err := http.NewRequest(http.MethodGet, url, nil)
- if err != nil {
- return "", err
- }
- resp, err := httpclient.GetHTTPClient().Do(req)
- if err != nil {
- return "", err
- }
- defer resp.Body.Close()
- return getCSRFTokenFromBody(resp.Body)
- }
- func getCSRFTokenFromBody(body io.Reader) (string, error) {
- doc, err := html.Parse(body)
- if err != nil {
- return "", err
- }
- var csrfToken string
- var f func(*html.Node)
- f = func(n *html.Node) {
- if n.Type == html.ElementNode && n.Data == "input" {
- var name, value string
- for _, attr := range n.Attr {
- if attr.Key == "value" {
- value = attr.Val
- }
- if attr.Key == "name" {
- name = attr.Val
- }
- }
- if name == csrfFormToken {
- csrfToken = value
- return
- }
- }
- for c := n.FirstChild; c != nil; c = c.NextSibling {
- f(c)
- }
- }
- f(doc)
- return csrfToken, nil
- }
- func getLoginForm(username, password, csrfToken string) url.Values {
- form := make(url.Values)
- form.Set("username", username)
- form.Set("password", password)
- form.Set(csrfFormToken, csrfToken)
- return form
- }
- func setCSRFHeaderForReq(req *http.Request, csrfToken string) {
- req.Header.Set("X-CSRF-TOKEN", csrfToken)
- }
- func setBearerForReq(req *http.Request, jwtToken string) {
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %v", jwtToken))
- }
- func setAPIKeyForReq(req *http.Request, apiKey, username string) {
- if username != "" {
- apiKey += "." + username
- }
- req.Header.Set("X-SFTPGO-API-KEY", apiKey)
- }
- func setJWTCookieForReq(req *http.Request, jwtToken string) {
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", jwtToken))
- }
- func getJWTAPITokenFromTestServer(username, password string) (string, error) {
- return getJWTAPITokenFromTestServerWithPasscode(username, password, "")
- }
- func getJWTAPITokenFromTestServerWithPasscode(username, password, passcode string) (string, error) {
- req, _ := http.NewRequest(http.MethodGet, tokenPath, nil)
- req.SetBasicAuth(username, password)
- if passcode != "" {
- req.Header.Set("X-SFTPGO-OTP", passcode)
- }
- rr := executeRequest(req)
- if rr.Code != http.StatusOK {
- return "", fmt.Errorf("unexpected status code %v", rr.Code)
- }
- responseHolder := make(map[string]any)
- err := render.DecodeJSON(rr.Body, &responseHolder)
- if err != nil {
- return "", err
- }
- return responseHolder["access_token"].(string), nil
- }
- func getJWTAPIUserTokenFromTestServer(username, password string) (string, error) {
- req, _ := http.NewRequest(http.MethodGet, userTokenPath, nil)
- req.SetBasicAuth(username, password)
- rr := executeRequest(req)
- if rr.Code != http.StatusOK {
- return "", fmt.Errorf("unexpected status code %v", rr.Code)
- }
- responseHolder := make(map[string]any)
- err := render.DecodeJSON(rr.Body, &responseHolder)
- if err != nil {
- return "", err
- }
- return responseHolder["access_token"].(string), nil
- }
- func getJWTWebToken(username, password string) (string, error) {
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- if err != nil {
- return "", err
- }
- form := getLoginForm(username, password, csrfToken)
- req, _ := http.NewRequest(http.MethodPost, httpBaseURL+webLoginPath,
- bytes.NewBuffer([]byte(form.Encode())))
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- client := &http.Client{
- Timeout: 10 * time.Second,
- CheckRedirect: func(req *http.Request, via []*http.Request) error {
- return http.ErrUseLastResponse
- },
- }
- resp, err := client.Do(req)
- if err != nil {
- return "", err
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusFound {
- return "", fmt.Errorf("unexpected status code %v", resp.StatusCode)
- }
- cookie := resp.Header.Get("Set-Cookie")
- if strings.HasPrefix(cookie, "jwt=") {
- return cookie[4:], nil
- }
- return "", errors.New("no cookie found")
- }
- func getCookieFromResponse(rr *httptest.ResponseRecorder) (string, error) {
- cookie := strings.Split(rr.Header().Get("Set-Cookie"), ";")
- if strings.HasPrefix(cookie[0], "jwt=") {
- return cookie[0][4:], nil
- }
- return "", errors.New("no cookie found")
- }
- func getJWTWebClientTokenFromTestServerWithAddr(username, password, remoteAddr string) (string, error) {
- csrfToken, err := getCSRFTokenMock(webClientLoginPath, remoteAddr)
- if err != nil {
- return "", err
- }
- form := getLoginForm(username, password, csrfToken)
- req, _ := http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = remoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr := executeRequest(req)
- if rr.Code != http.StatusFound {
- return "", fmt.Errorf("unexpected status code %v", rr)
- }
- return getCookieFromResponse(rr)
- }
- func getJWTWebClientTokenFromTestServer(username, password string) (string, error) {
- csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
- if err != nil {
- return "", err
- }
- form := getLoginForm(username, password, csrfToken)
- req, _ := http.NewRequest(http.MethodPost, webClientLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr := executeRequest(req)
- if rr.Code != http.StatusFound {
- return "", fmt.Errorf("unexpected status code %v", rr)
- }
- return getCookieFromResponse(rr)
- }
- func getJWTWebTokenFromTestServer(username, password string) (string, error) {
- csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
- if err != nil {
- return "", err
- }
- form := getLoginForm(username, password, csrfToken)
- req, _ := http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
- req.RemoteAddr = defaultRemoteAddr
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- rr := executeRequest(req)
- if rr.Code != http.StatusFound {
- return "", fmt.Errorf("unexpected status code %v", rr)
- }
- return getCookieFromResponse(rr)
- }
- func executeRequest(req *http.Request) *httptest.ResponseRecorder {
- rr := httptest.NewRecorder()
- testServer.Config.Handler.ServeHTTP(rr, req)
- return rr
- }
- func checkResponseCode(t *testing.T, expected int, rr *httptest.ResponseRecorder) {
- assert.Equal(t, expected, rr.Code, rr.Body.String())
- }
- func createTestFile(path string, size int64) error {
- baseDir := filepath.Dir(path)
- if _, err := os.Stat(baseDir); errors.Is(err, fs.ErrNotExist) {
- err = os.MkdirAll(baseDir, os.ModePerm)
- if err != nil {
- return err
- }
- }
- content := make([]byte, size)
- if size > 0 {
- _, err := rand.Read(content)
- if err != nil {
- return err
- }
- }
- return os.WriteFile(path, content, os.ModePerm)
- }
- func getExitCodeScriptContent(exitCode int) []byte {
- content := []byte("#!/bin/sh\n\n")
- content = append(content, []byte(fmt.Sprintf("exit %v", exitCode))...)
- return content
- }
- func getMultipartFormData(values url.Values, fileFieldName, filePath string) (bytes.Buffer, string, error) {
- var b bytes.Buffer
- w := multipart.NewWriter(&b)
- for k, v := range values {
- for _, s := range v {
- if err := w.WriteField(k, s); err != nil {
- return b, "", err
- }
- }
- }
- if len(fileFieldName) > 0 && len(filePath) > 0 {
- fw, err := w.CreateFormFile(fileFieldName, filepath.Base(filePath))
- if err != nil {
- return b, "", err
- }
- f, err := os.Open(filePath)
- if err != nil {
- return b, "", err
- }
- defer f.Close()
- if _, err = io.Copy(fw, f); err != nil {
- return b, "", err
- }
- }
- err := w.Close()
- return b, w.FormDataContentType(), err
- }
- func generateTOTPPasscode(secret string) (string, error) {
- return totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{
- Period: 30,
- Skew: 1,
- Digits: otp.DigitsSix,
- Algorithm: otp.AlgorithmSHA1,
- })
- }
- func isDbDefenderSupported() bool {
- // SQLite shares the implementation with other SQL-based provider but it makes no sense
- // to use it outside test cases
- switch dataprovider.GetProviderStatus().Driver {
- case dataprovider.MySQLDataProviderName, dataprovider.PGSQLDataProviderName,
- dataprovider.CockroachDataProviderName, dataprovider.SQLiteDataProviderName:
- return true
- default:
- return false
- }
- }
- func BenchmarkSecretDecryption(b *testing.B) {
- s := kms.NewPlainSecret("test data")
- s.SetAdditionalData("username")
- err := s.Encrypt()
- require.NoError(b, err)
- for i := 0; i < b.N; i++ {
- err = s.Clone().Decrypt()
- require.NoError(b, err)
- }
- }
|