| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020 |
- package httpd_test
- import (
- "bytes"
- "crypto/rand"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "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/lib/pq"
- "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/common"
- "github.com/drakkan/sftpgo/v2/config"
- "github.com/drakkan/sftpgo/v2/dataprovider"
- "github.com/drakkan/sftpgo/v2/httpclient"
- "github.com/drakkan/sftpgo/v2/httpd"
- "github.com/drakkan/sftpgo/v2/httpdtest"
- "github.com/drakkan/sftpgo/v2/kms"
- "github.com/drakkan/sftpgo/v2/logger"
- "github.com/drakkan/sftpgo/v2/mfa"
- "github.com/drakkan/sftpgo/v2/plugin"
- "github.com/drakkan/sftpgo/v2/sftpd"
- "github.com/drakkan/sftpgo/v2/smtp"
- "github.com/drakkan/sftpgo/v2/util"
- "github.com/drakkan/sftpgo/v2/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"
- activeConnectionsPath = "/api/v2/connections"
- serverStatusPath = "/api/v2/status"
- quotasBasePath = "/api/v2/quotas"
- quotaScanPath = "/api/v2/quotas/users/scans"
- quotaScanVFolderPath = "/api/v2/quotas/folders/scans"
- quotaScanCompatPath = "/api/v2/quota-scans"
- quotaScanVFolderCompatPath = "/api/v2/folder-quota-scans"
- updateUsedQuotaCompatPath = "/api/v2/quota-update"
- updateFolderUsedQuotaCompatPath = "/api/v2/folder-quota-update"
- defenderHosts = "/api/v2/defender/hosts"
- defenderBanTime = "/api/v2/defender/bantime"
- defenderUnban = "/api/v2/defender/unban"
- defenderScore = "/api/v2/defender/score"
- versionPath = "/api/v2/version"
- logoutPath = "/api/v2/logout"
- userPwdPath = "/api/v2/user/changepwd"
- userPublicKeysPath = "/api/v2/user/publickeys"
- 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"
- healthzPath = "/healthz"
- 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"
- 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"
- 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"
- 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"
- configDir = ".."
- 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"
- redactedSecret = "[**redacted**]"
- osWindows = "windows"
- oidcMockAddr = "127.0.0.1:11111"
- )
- var (
- defaultPerms = []string{dataprovider.PermAny}
- homeBasePath string
- backupsPath string
- credentialsPath string
- testServer *httptest.Server
- providerDriverName string
- 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_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()
- credentialsPath = filepath.Join(os.TempDir(), "test_credentials")
- providerConf.CredentialsPath = credentialsPath
- providerDriverName = providerConf.Driver
- os.RemoveAll(credentialsPath) //nolint:errcheck
- 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, true)
- 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); 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.CertificateFile = certPath
- httpdConf.CertificateKeyFile = keyPath
- httpdConf.Bindings = append(httpdConf.Bindings, httpd.Binding{})
- go func() {
- if err := httpdConf.Initialize(configDir); 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.RemoveAll(credentialsPath)
- 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) {
- 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)
- assert.Error(t, err)
- httpdConf.CertificateFile = ""
- httpdConf.CertificateKeyFile = ""
- httpdConf.TemplatesPath = "."
- err = httpdConf.Initialize(configDir)
- assert.Error(t, err)
- httpdConf = config.GetHTTPDConfig()
- httpdConf.TemplatesPath = defaultTemplatesPath
- httpdConf.CertificateFile = invalidFile
- httpdConf.CertificateKeyFile = invalidFile
- httpdConf.StaticFilesPath = ""
- httpdConf.TemplatesPath = ""
- err = httpdConf.Initialize(configDir)
- 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)
- assert.Error(t, err)
- httpdConf.CACertificates = nil
- httpdConf.CARevocationLists = append(httpdConf.CARevocationLists, invalidFile)
- err = httpdConf.Initialize(configDir)
- assert.Error(t, err)
- httpdConf.CARevocationLists = nil
- httpdConf.Bindings[0].ProxyAllowed = []string{"invalid ip/network"}
- err = httpdConf.Initialize(configDir)
- 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
- err = httpdConf.Initialize(configDir)
- 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)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "oidc")
- }
- httpdConf.Bindings[0].OIDC.UsernameField = "preferred_username"
- err = httpdConf.Initialize(configDir)
- if assert.Error(t, err) {
- assert.Contains(t, err.Error(), "oidc")
- }
- }
- func TestBasicUserHandling(t *testing.T) {
- u := getTestUser()
- u.Email = "[email protected]"
- user, resp, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err, string(resp))
- 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 = "[email protected]"
- 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)
- user, _, err = httpdtest.GetUserByUsername(defaultUsername, http.StatusOK)
- assert.NoError(t, err)
- 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 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.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, 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, 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, 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 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]interface{})
- 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]interface{})
- 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, userPublicKeysPath), 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)
- // using the admin token should not work
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userPublicKeysPath), 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", 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)
- req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%v%v", httpBaseURL, userPublicKeysPath), 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)
- _, 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)
- user.Filters.RecoveryCodes = []dataprovider.RecoveryCode{
- {
- Secret: kms.NewPlainSecret(util.GenerateUniqueID()),
- },
- }
- user, resp, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(resp))
- assert.Contains(t, user.Filters.WebClient, sdk.WebClientMFADisabled)
- assert.Len(t, user.Filters.RecoveryCodes, 12)
- 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)
- _, 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]interface{})
- 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]interface{})
- 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]interface{})
- 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)
- 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]interface{})
- 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)
- _, 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]interface{})
- 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, _, err = httpdtest.AddAdmin(admin, http.StatusCreated)
- assert.NoError(t, err)
- admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
- assert.NoError(t, err)
- admin.AdditionalInfo = "test info"
- admin, _, err = httpdtest.UpdateAdmin(admin, http.StatusOK)
- assert.NoError(t, err)
- assert.Equal(t, "test info", admin.AdditionalInfo)
- 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 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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- }
- 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]interface{})
- 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]interface{})
- 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{}
- _, _, err := httpdtest.AddUser(u, http.StatusBadRequest)
- 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)
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- err = os.MkdirAll(credentialsPath, 0700)
- 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 = 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")
- }
- }
- 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 TestMetadataAPI(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 []interface{}
- 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 []interface{}
- 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 := []common.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)
- c.Start()
- 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", // 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"),
- Name: folderName,
- },
- VirtualPath: "/", // 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(u.GetHomeDir(), "mapped_dir"), // invalid, inside home dir
- Name: folderName,
- },
- VirtualPath: "/vdir",
- })
- _, _, err = httpdtest.AddUser(u, http.StatusBadRequest)
- assert.NoError(t, err)
- u.VirtualFolders = nil
- u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
- BaseVirtualFolder: vfs.BaseVirtualFolder{
- MappedPath: u.GetHomeDir(), // invalid
- Name: folderName,
- },
- VirtualPath: "/vdir",
- })
- _, _, 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(u.GetHomeDir(), ".."), // invalid, contains home dir
- Name: "tmp",
- },
- VirtualPath: "/vdir",
- })
- _, _, 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: "/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"), // invalid, already defined
- Name: folderName,
- },
- 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]interface{})
- 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.Region = "us-east-1" //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 TestUserGCSConfig(t *testing.T) {
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.PreferDatabaseCredentials = false
- providerConf.CredentialsPath = credentialsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- err = os.MkdirAll(credentialsPath, 0700)
- assert.NoError(t, err)
- user.FsConfig.Provider = sdk.GCSFilesystemProvider
- user.FsConfig.GCSConfig.Bucket = "test"
- user.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("fake credentials") //nolint:goconst
- user, bb, err := httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err, string(bb))
- credentialFile := filepath.Join(credentialsPath, fmt.Sprintf("%v_gcs_credentials.json", user.Username))
- assert.FileExists(t, credentialFile)
- creds, err := os.ReadFile(credentialFile)
- assert.NoError(t, err)
- secret := kms.NewEmptySecret()
- err = json.Unmarshal(creds, secret)
- assert.NoError(t, err)
- err = secret.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, "fake credentials", secret.GetPayload())
- user.FsConfig.GCSConfig.Credentials = kms.NewSecret(sdkkms.SecretStatusSecretBox, "fake encrypted credentials", "", "")
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.FileExists(t, credentialFile)
- creds, err = os.ReadFile(credentialFile)
- assert.NoError(t, err)
- secret = kms.NewEmptySecret()
- err = json.Unmarshal(creds, secret)
- assert.NoError(t, err)
- err = secret.Decrypt()
- assert.NoError(t, err)
- assert.Equal(t, "fake credentials", secret.GetPayload())
- _, err = httpdtest.RemoveUser(user, http.StatusOK)
- assert.NoError(t, err)
- user.Password = defaultPassword
- user.ID = 0
- user.CreatedAt = 0
- user.FsConfig.GCSConfig.Credentials = kms.NewSecret(sdkkms.SecretStatusSecretBox, "fake credentials", "", "")
- _, _, err = httpdtest.AddUser(user, http.StatusCreated)
- assert.Error(t, err)
- user.FsConfig.GCSConfig.Credentials.SetStatus(sdkkms.SecretStatusPlain)
- user, body, err := httpdtest.AddUser(user, http.StatusCreated)
- assert.NoError(t, err, string(body))
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- err = os.MkdirAll(credentialsPath, 0700)
- assert.NoError(t, err)
- user.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
- user.FsConfig.GCSConfig.AutomaticCredentials = 1
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- assert.NoFileExists(t, credentialFile)
- user.FsConfig.GCSConfig = vfs.GCSFsConfig{}
- user.FsConfig.Provider = sdk.S3FilesystemProvider
- user.FsConfig.S3Config.Bucket = "test1"
- user.FsConfig.S3Config.Region = "us-east-1"
- user.FsConfig.S3Config.AccessKey = "Server-Access-Key1"
- user.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret("secret")
- user.FsConfig.S3Config.Endpoint = "http://localhost:9000"
- user.FsConfig.S3Config.KeyPrefix = "somedir/subdir"
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- user.FsConfig.S3Config = vfs.S3FsConfig{}
- user.FsConfig.Provider = sdk.GCSFilesystemProvider
- user.FsConfig.GCSConfig.Bucket = "test1"
- user.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret("fake credentials")
- user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- _, 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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- err = dataprovider.Initialize(providerConf, configDir, true)
- 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 = "127.0.0.1" // missing port
- 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
- _, 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: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) {
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.PreferDatabaseCredentials = true
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- // sensitive data must be hidden but not deleted from the dataprovider
- usernames := []string{"user1", "user2", "user3", "user4", "user5"}
- 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)
- users, _, err := httpdtest.GetUsers(0, 0, http.StatusOK)
- assert.NoError(t, err)
- assert.GreaterOrEqual(t, len(users), 5)
- 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)
- // 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())
- // 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 = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- err = dataprovider.Initialize(providerConf, configDir, true)
- 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,
- }
- common.Connections.Add(fakeConn)
- _, 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,
- }
- common.Connections.Add(fakeConn)
- c1 := common.NewBaseConnection("connID1", common.ProtocolSFTP, "", "", user)
- fakeConn1 := &fakeConnection{
- BaseConnection: c1,
- }
- common.Connections.Add(fakeConn1)
- 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)
- common.Connections.Add(fakeConn)
- common.Connections.Add(fakeConn1)
- 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 TestNamingRules(t *testing.T) {
- smtpCfg := smtp.Config{
- Host: "127.0.0.1",
- Port: 3525,
- TemplatesPath: "templates",
- }
- err := smtpCfg.Initialize("..")
- 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 = " [email protected] "
- u.Email = dataprovider.ConvertName(u.Username)
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, "[email protected]", user.Username)
- 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)
- a := getTestAdmin()
- a.Username = "[email protected] "
- admin, _, err := httpdtest.AddAdmin(a, http.StatusCreated)
- assert.NoError(t, err)
- assert.Equal(t, "[email protected]", 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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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
- 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(), "Unable to set the new password")
- 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")
- 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")
- 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")
- // 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)
- smtpCfg = smtp.Config{}
- err = smtpCfg.Initialize("..")
- 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 = "[email protected]"
- 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 = "[email protected]"
- 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)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- if config.GetProviderConf().Driver == dataprovider.MemoryDataProviderName {
- return
- }
- 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)
- }
- 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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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.GetAdmins(1, 0, http.StatusInternalServerError)
- assert.NoError(t, err)
- _, _, err = httpdtest.GetAPIKeys(1, 0, 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)
- // 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+"/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.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)
- 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, webTemplateFolder+"?from=afolder", 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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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["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.CredentialsPath = credentialsPath
- providerConf.BackupsPath = backupsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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"
- response, _, err := httpdtest.GetBanTime(ip, http.StatusOK)
- assert.NoError(t, err)
- banTime, ok := response["date_time"]
- assert.True(t, ok)
- assert.Nil(t, banTime)
- hosts, _, err := httpdtest.GetDefenderHosts(http.StatusOK)
- assert.NoError(t, err)
- assert.Len(t, hosts, 0)
- response, _, err = httpdtest.GetScore(ip, http.StatusOK)
- assert.NoError(t, err)
- score, ok := response["score"]
- assert.True(t, ok)
- assert.Equal(t, float64(0), score)
- err = httpdtest.UnbanIP(ip, http.StatusNotFound)
- assert.NoError(t, err)
- _, err = httpdtest.RemoveDefenderHostByIP(ip, http.StatusNotFound)
- assert.NoError(t, err)
- common.AddDefenderEvent(ip, common.HostEventNoLoginTried)
- response, _, err = httpdtest.GetScore(ip, http.StatusOK)
- assert.NoError(t, err)
- score, ok = response["score"]
- assert.True(t, ok)
- assert.Equal(t, float64(2), score)
- 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)
- response, _, err = httpdtest.GetBanTime(ip, http.StatusOK)
- assert.NoError(t, err)
- banTime, ok = response["date_time"]
- assert.True(t, ok)
- assert.NotNil(t, banTime)
- 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.UnbanIP(ip, http.StatusOK)
- assert.NoError(t, err)
- err = httpdtest.UnbanIP(ip, http.StatusNotFound)
- assert.NoError(t, err)
- host, _, 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) {
- _, _, err := httpdtest.GetBanTime("", http.StatusBadRequest)
- require.NoError(t, err)
- _, _, err = httpdtest.GetBanTime("invalid", http.StatusBadRequest)
- require.NoError(t, err)
- _, _, err = httpdtest.GetScore("", http.StatusBadRequest)
- require.NoError(t, err)
- err = httpdtest.UnbanIP("", http.StatusBadRequest)
- require.NoError(t, err)
- 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)
- ip := "127.1.1.2"
- req, err := http.NewRequest(http.MethodGet, defenderHosts, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, defenderBanTime+"?ip="+ip, nil)
- assert.NoError(t, err)
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusInternalServerError, rr)
- req, err = http.NewRequest(http.MethodGet, defenderScore+"?ip="+ip, 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.CredentialsPath = credentialsPath
- providerConf.BackupsPath = backupsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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)
- user := getTestUser()
- user.ID = 1
- user.Username = "test_user_restored"
- admin := getTestAdmin()
- admin.ID = 1
- admin.Username = "test_admin_restored"
- backupData := dataprovider.BackupData{}
- backupData.Users = append(backupData.Users, user)
- backupData.Admins = append(backupData.Admins, admin)
- 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)
- _, _, err = httpdtest.LoaddataFromPostBody(backupContent, "0", "0", http.StatusOK)
- assert.NoError(t, err)
- user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
- assert.NoError(t, err)
- _, err = dataprovider.ShareExists(keyID, user.Username)
- 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)
- 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)
- foldeDesc := "restored folder desc"
- user := getTestUser()
- user.ID = 1
- user.Username = "test_user_restore"
- 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()),
- }
- share := dataprovider.Share{
- ShareID: util.GenerateUniqueID(),
- Name: util.GenerateUniqueID(),
- Scope: dataprovider.ShareScopeRead,
- Paths: []string{"/"},
- Username: user.Username,
- }
- backupData := dataprovider.BackupData{}
- backupData.Users = append(backupData.Users, user)
- 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: foldeDesc,
- },
- }
- backupData.APIKeys = append(backupData.APIKeys, apiKey)
- backupData.Shares = append(backupData.Shares, share)
- 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 user, folder, admin, API key, share from backup
- _, _, err = httpdtest.Loaddata(backupFilePath, "1", "", http.StatusOK)
- assert.NoError(t, err)
- // 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)
- _, err = dataprovider.ShareExists(share.ShareID, user.Username)
- 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)
- apiKey, _, err = httpdtest.GetAPIKeyByID(apiKey.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, 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, foldeDesc, folder.Description)
- assert.Len(t, folder.Users, 0)
- _, err = httpdtest.RemoveFolder(folder, 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)
- user := getTestUser()
- user.ID = 1
- user.Username = "test_user_restore"
- 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,
- }
- backupData := dataprovider.BackupData{}
- backupData.Users = append(backupData.Users, user)
- backupData.Admins = append(backupData.Admins, admin)
- 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)
- oldUploadBandwidth := user.UploadBandwidth
- user.UploadBandwidth = oldUploadBandwidth + 128
- user, _, err = httpdtest.UpdateUser(user, 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)
- backupData.Folders = []vfs.BaseVirtualFolder{
- {
- MappedPath: mappedPath,
- Name: folderName,
- },
- }
- _, _, err = httpdtest.Loaddata(backupFilePath, "0", "1", 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)
- c := common.NewBaseConnection("connID", common.ProtocolFTP, "", "", user)
- fakeConn := &fakeConnection{
- BaseConnection: c,
- }
- common.Connections.Add(fakeConn)
- 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)
- _, _, 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)
- _, err = httpdtest.RemoveUser(user, 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 = 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.IsStringInSlice(dataprovider.PermAny, updatedUser.Permissions["/"]))
- 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 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 TestUnbanInvalidJsonMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, defenderUnban, bytes.NewBuffer([]byte("invalid json")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- 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 should get no recovery codes
- req, err = http.NewRequest(http.MethodGet, admin2FARecoveryCodesPath, 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, 0)
- // 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)
- recCodes = nil
- 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)
- 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]interface{}, 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", "instance_id"} {
- _, ok := ev[field]
- assert.True(t, ok, field)
- }
- }
- // 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)
- 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]interface{}, 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", "instance_id"} {
- _, ok := ev[field]
- assert.True(t, ok, field)
- }
- }
- // 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)
- 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)
- }
- 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 := "[email protected]"
- description := "user API description"
- profileReq := make(map[string]interface{})
- 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]interface{})
- 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"].([]interface{}), 2)
- // set an invalid email
- profileReq = make(map[string]interface{})
- 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]interface{})
- 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]interface{})
- 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]interface{})
- 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"].([]interface{}), 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]interface{})
- profileReq["allow_api_key_auth"] = false
- profileReq["email"] = "[email protected]"
- 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]interface{})
- 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"].([]interface{}), 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 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 := "[email protected]"
- description := "admin API description"
- profileReq := make(map[string]interface{})
- 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]interface{})
- 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)
- 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)
- 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.IsStringInSlice(v, perms))
- }
- } 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.MethodPut, updateUsedQuotaCompatPath, 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, updateUsedQuotaCompatPath, bytes.NewBuffer([]byte("string")))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- 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.IsStringInSlice(dataprovider.PermListItems, val))
- 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 && os.IsNotExist(err) {
- 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)
- asJSON, err := json.Marshal(user)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPost, quotaScanCompatPath, bytes.NewBuffer(asJSON))
- 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)
- req, _ = http.NewRequest(http.MethodPut, updateFolderUsedQuotaCompatPath, 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, updateFolderUsedQuotaCompatPath, bytes.NewBuffer([]byte("string")))
- setBearerForReq(req, token)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- 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 && os.IsNotExist(err) {
- 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)
- asJSON, err := json.Marshal(folder)
- assert.NoError(t, err)
- req, _ = http.NewRequest(http.MethodPost, quotaScanVFolderCompatPath, bytes.NewBuffer(asJSON))
- 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 TestStartQuotaScanBadUserMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, quotaScanCompatPath, bytes.NewBuffer([]byte("invalid json")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- }
- func TestStartQuotaScanBadFolderMock(t *testing.T) {
- token, err := getJWTAPITokenFromTestServer(defaultTokenAuthUser, defaultTokenAuthPass)
- assert.NoError(t, err)
- req, _ := http.NewRequest(http.MethodPost, quotaScanVFolderCompatPath, bytes.NewBuffer([]byte("invalid json")))
- setBearerForReq(req, token)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, 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)
- }
- 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, "/healthz", nil)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- assert.Equal(t, "ok", 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")
- req, _ = http.NewRequest(http.MethodGet, userPublicKeysPath, 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.MethodPut, userPublicKeysPath, 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),
- }
- common.Connections.Add(connection)
- _, 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(), "connection not allowed from your ip")
- 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 = "[email protected]"
- user, _, err := httpdtest.AddUser(u, http.StatusCreated)
- assert.NoError(t, err)
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- _, 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),
- }
- common.Connections.Add(connection)
- _, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- _, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.Error(t, err)
- // test reset password
- smtpCfg := smtp.Config{
- Host: "127.0.0.1",
- Port: 3525,
- TemplatesPath: "templates",
- }
- err = smtpCfg.Initialize("..")
- 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("..")
- 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 = "[email protected]"
- 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("..")
- 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("..")
- 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) {
- err := dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.PreferDatabaseCredentials = false
- providerConf.CredentialsPath = credentialsPath
- err = dataprovider.Initialize(providerConf, configDir, true)
- assert.NoError(t, err)
- 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)
- credentialsFile := filepath.Join(credentialsPath, fmt.Sprintf("%v_gcs_credentials.json", u.Username))
- if !filepath.IsAbs(credentialsFile) {
- credentialsFile = filepath.Join(configDir, credentialsFile)
- }
- // now remove the credentials file so the filesystem creation will fail
- err = os.Remove(credentialsFile)
- 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)
- err = dataprovider.Close()
- assert.NoError(t, err)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf = config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- err = dataprovider.Initialize(providerConf, configDir, true)
- 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 TestWebAPIPublicKeys(t *testing.T) {
- user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
- assert.NoError(t, err)
- apiToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err := http.NewRequest(http.MethodGet, userPublicKeysPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr := executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- var keys []string
- err = json.Unmarshal(rr.Body.Bytes(), &keys)
- assert.NoError(t, err)
- assert.Len(t, keys, 0)
- keys = []string{testPubKey, testPubKey1}
- asJSON, err := json.Marshal(keys)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userPublicKeysPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- req, err = http.NewRequest(http.MethodGet, userPublicKeysPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- 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, 2)
- req, err = http.NewRequest(http.MethodPut, userPublicKeysPath, bytes.NewBuffer([]byte(`invalid json`)))
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- keys = []string{`not a public key`}
- asJSON, err = json.Marshal(keys)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodPut, userPublicKeysPath, bytes.NewBuffer(asJSON))
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusBadRequest, rr)
- assert.Contains(t, rr.Body.String(), "could not parse key")
- user.Filters.WebClient = append(user.Filters.WebClient, sdk.WebClientPubKeyChangeDisabled)
- _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
- assert.NoError(t, err)
- apiToken, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
- assert.NoError(t, err)
- req, err = http.NewRequest(http.MethodGet, userPublicKeysPath, nil)
- assert.NoError(t, err)
- setBearerForReq(req, apiToken)
- 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]interface{})
- 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")
- 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!")
- share.Scope = dataprovider.ShareScopeRead
- share.Paths = []string{"/missing"}
- 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 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))
- }
- // we don't allow to create the file in subdirectories
- req, err = http.NewRequest(http.MethodPost, path.Join(sharesPath, objectID, "%2Fdir%2Ffile1.txt"), bytes.NewBuffer(content))
- assert.NoError(t, err)
- req.SetBasicAuth(defaultUsername, defaultPassword)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusForbidden, rr)
- 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 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)
- 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
- 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)
- 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]interface{}, 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]interface{}, 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]interface{}, 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(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(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)
- // 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.StatusInternalServerError, 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]interface{}, 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")
- _, 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]interface{}
- 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, webClientViewPDFPath+"?path=test.pdf", 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 = os.RemoveAll(user.GetHomeDir())
- assert.NoError(t, err)
- }
- 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]interface{}
- 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())
- 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", time.Now().UTC().Add(120*time.Second).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", time.Now().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", time.Now().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", time.Now().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", time.Now().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", time.Now().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", time.Now().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]interface{}
- 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)
- // 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)
- // 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]interface{}
- err = json.NewDecoder(rr.Body).Decode(&contents)
- assert.NoError(t, err)
- assert.Len(t, contents, 2)
- // 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]interface{}
- 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)
- 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 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.FsConfig.SFTPConfig.BufferSize = 2
- u.Permissions["/adir"] = nil
- u.Permissions["/adir1"] = []string{dataprovider.PermListItems}
- u.Filters.FilePatterns = []sdk.PatternsFilter{
- {
- Path: "/adir2",
- DeniedPatterns: []string{"*.txt"},
- },
- }
- sftpUser, _, 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)
- 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 = os.RemoveAll(sftpUser.GetHomeDir())
- 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()
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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(), "connection not allowed from your ip")
- 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(), "connection not allowed from your ip")
- 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}
- user, _, err := httpdtest.AddUser(u, 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: "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, 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 := "[email protected]"
- 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", "[email protected]")
- 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", "[email protected]")
- 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, "[email protected]", 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, _, 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]interface{}
- 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)
- 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")
- 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)
- 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", "[email protected]")
- 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, "[email protected]", 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.MethodDelete, path.Join(webAdminPath, altAdminUsername), nil)
- req.RemoteAddr = defaultRemoteAddr
- setJWTCookieForReq(req, token)
- setCSRFHeaderForReq(req, csrfToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, rr)
- _, 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 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")
- }
- 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)
- 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 = "[email protected]"
- 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("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")
- 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", "1000")
- // 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.True(t, util.IsStringInSlice(testPubKey, newUser.PublicKeys))
- if val, ok := newUser.Permissions["/subdir"]; ok {
- assert.True(t, util.IsStringInSlice(dataprovider.PermListItems, val))
- assert.True(t, util.IsStringInSlice(dataprovider.PermDownload, val))
- } 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.IsStringInSlice("*.png", filter.AllowedPatterns))
- assert.True(t, util.IsStringInSlice("*.zip", filter.DeniedPatterns))
- 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.IsStringInSlice("*.jpg", filter.AllowedPatterns))
- assert.True(t, util.IsStringInSlice("*.png", filter.AllowedPatterns))
- assert.True(t, util.IsStringInSlice("*.mkv", filter.DeniedPatterns))
- 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.IsStringInSlice("*.rar", filter.DeniedPatterns))
- 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.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)
- }
- 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)
- // 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)
- 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 = 3
- user.GID = 1000
- user.Filters.AllowAPIKeyAuth = true
- user.AdditionalInfo = "new additional info"
- user.Email = "[email protected]"
- 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", 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("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("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("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)
- if val, ok := updateUser.Permissions["/otherdir"]; ok {
- assert.True(t, util.IsStringInSlice(dataprovider.PermListItems, val))
- assert.True(t, util.IsStringInSlice(dataprovider.PermUpload, val))
- } else {
- assert.Fail(t, "user permissions must contains /otherdir", "actual: %v", updateUser.Permissions)
- }
- assert.True(t, util.IsStringInSlice("192.168.1.3/32", updateUser.Filters.AllowedIP))
- assert.True(t, util.IsStringInSlice("10.0.0.2/32", updateUser.Filters.DeniedIP))
- assert.True(t, util.IsStringInSlice(dataprovider.SSHLoginMethodKeyboardInteractive, updateUser.Filters.DeniedLoginMethods))
- assert.True(t, util.IsStringInSlice(common.ProtocolFTP, updateUser.Filters.DeniedProtocols))
- assert.True(t, util.IsStringInSlice("*.zip", updateUser.Filters.FilePatterns[0].DeniedPatterns))
- 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("external_auth_cache_time", "0")
- form.Set("description", "desc %username% %password%")
- 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, path.Join(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, path.Join(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, path.Join(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, 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)
- _, 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("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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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.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", "user1")
- form.Set("tpl_password", " ")
- form.Set("tpl_public_keys", "")
- 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", "user3")
- form.Add("tpl_password", "")
- form.Add("tpl_public_keys", "")
- 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")
- 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
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- 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)
- 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("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.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)
- // 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())
- // 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")
- 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")
- }
- 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 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")
- // 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")
- // 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.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")
- // 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_fingerprints", user.FsConfig.SFTPConfig.Fingerprints[0])
- form.Set("sftp_prefix", user.FsConfig.SFTPConfig.Prefix)
- form.Set("sftp_disable_concurrent_reads", "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, 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)
- // now check that a redacted credentials are not saved
- form.Set("sftp_password", redactedSecret+" ")
- form.Set("sftp_private_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.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())
- req, _ = http.NewRequest(http.MethodDelete, path.Join(userPath, user.Username), nil)
- setBearerForReq(req, apiToken)
- rr = executeRequest(req)
- checkResponseCode(t, http.StatusOK, 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 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 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("..")
- 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("..")
- 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("..")
- 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("..")
- require.NoError(t, err)
- u := getTestUser()
- u.Email = "[email protected]"
- 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("..")
- 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("..")
- 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 = "[email protected]"
- 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 = "[email protected]"
- 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("..")
- 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)
- 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)
- err = config.LoadConfig(configDir, "")
- assert.NoError(t, err)
- providerConf := config.GetProviderConf()
- providerConf.BackupsPath = backupsPath
- providerConf.CredentialsPath = credentialsPath
- err = os.RemoveAll(credentialsPath)
- assert.NoError(t, err)
- err = dataprovider.Initialize(providerConf, configDir, true)
- 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 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, r *http.Request) {
- fmt.Fprintf(w, "OK\n")
- })
- http.HandleFunc("/auth/realms/sftpgo/.well-known/openid-configuration", func(w http.ResponseWriter, r *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, r *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(remoteAddr net.Addr, from string, to []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: "[email protected]",
- Description: "test admin",
- }
- }
- 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) {
- req, _ := http.NewRequest(http.MethodGet, tokenPath, 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]interface{})
- 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]interface{})
- 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); os.IsNotExist(err) {
- 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)
- }
- }
|