PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB` sh-3ll

HOME


sh-3ll 1.0
DIR:/proc/self/root/usr/share/filebeat/module/o365/audit/config/
Upload File :
Current File : //proc/self/root/usr/share/filebeat/module/o365/audit/config/pipeline.js
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

var processor = require("processor");
var console   = require("console");

// PipelineBuilder to aid debugging of pipelines during development.
function PipelineBuilder(pipelineName, debug) {
    this.pipeline = new processor.Chain();
    this.add = function (processor) {
        this.pipeline = this.pipeline.Add(processor);
    };
    this.Add = function (name, processor) {
        this.add(processor);
        if (debug) {
            this.add(makeLogEvent("after " + pipelineName + "/" + name));
        }
    };
    this.Build = function () {
        if (debug) {
            this.add(makeLogEvent(pipelineName + "processing done"));
        }
        return this.pipeline.Build();
    };
    if (debug) {
        this.add(makeLogEvent(pipelineName + ": begin processing event"));
    }
}

function appendFields(options) {
    return function(evt) {
        options.fields.forEach(function (key) {
            var value = evt.Get(key);
            if (value != null) evt.AppendTo(options.to, value);
        });
    }
}

// logEvent(msg)
//
// Processor that logs the current value of evt to console.debug.
function makeLogEvent(msg) {
    return function (evt) {
        console.debug(msg + " :" +  JSON.stringify(evt, null, 4));
    };
}

// makeConditional({condition:expr, result1:processor|expr, [...]})
//
// Processor that selects which processor to run depending on the result of
// evaluating a _condition_. Result can be boolean (if-else equivalent) or any
// other value (switch equivalent). Unspecified values are a no-op.
function makeConditional(options) {
    return function (evt) {
        var branch = options[options.condition(evt)] || function(evt){};
        return (typeof branch === "function" ? branch : branch.Run)(evt);
    };
}

// makeMapper({from:field, to:field, default:value mappings:{orig: new, [...]}})
//
// Processor that sets the `to` field by mapping of `from` field's value.
function makeMapper(options) {
    return function (evt) {
        var key = evt.Get(options.from);
        if (key == null && options.skip_missing) return;
        if (options.lowercase && typeof key == "string") {
            key = key.toLowerCase();
        }
        var value = options.default;
        if (key in options.mappings) {
            value = options.mappings[key];
        } else if (typeof value === "function") {
            value = value(key);
        }
        if (value != null) {
            evt.Put(options.to, value);
        }
    };
}

// Makes sure a name can be used as a field in the output document.
function validFieldName(s) {
    return s.replace(/[\ \.]/g, '_')
}

/* Turns a `common.NameValuePair` array into an object. Multiple-value fields
   are stored as arrays.
 input (a NameValuePair array):
     from_field: [
        {Name: name1, Value: value1},
        {Name: name2, Value: value2},
        {Name: name2, Value: value2b},
        [...]
        {Name: nameN, Value: valueN}
     ]

 output (an object):
     to_field: {
        name1: value1,
        name2: [value2, value2b],
        [...]
        nameN: valueN
     }
*/
function makeObjFromNameValuePairArray(options) {
    return function(evt) {
        var src = evt.Get(options.from);
        var dict = {};
        if (src == null) return;
        if (!(src instanceof Array)) {
            evt.Put(options.to, {"_raw": src} );
            return;
        }
        for (var i=0; i < src.length; i++) {
            var name, value;
            if (src[i] == null
                || (name=src[i].Name) == null
                || (value=src[i].Value) == null) continue;
            name = validFieldName(name);
            if (name in dict) {
                if (dict[name] instanceof Array) {
                    dict[name].push(value);
                } else {
                    dict[name] = [value];
                }
            } else {
                dict[name] = value;
            }
        }
        evt.Put(options.to, dict);
    }
}

/* Converts a Common.ModifiedProperty array into an object.
   input:
    from_field: [
        {Name: name1, OldValue: old1, NewValue: new1},
        {Name: name2, OldValue: old2, NewValue: new2},
        {Name: name2, OldValue: old2b, NewValue: new2b},
        [...]
        {Name: nameN, OldValue: oldN, NewValue: newN},
    ],

    output:
    to_field: {
        name1: { OldValue: old1, NewValue: new1 },
        name2: { OldValue: [old2, old2b], NewValue: [new2, new2b] },
        [...]
        nameN: { OldValue: oldN, NewValue: newN }
    }
 */
function makeDictFromModifiedPropertyArray(options) {
    return function(evt) {
        var src = evt.Get(options.from);
        var dict = {};
        if (src == null || !(src instanceof Array)) return;
        for (var i=0; i < src.length; i++) {
            var name, newValue, oldValue;
            if (src[i] == null
                || (name=src[i].Name) == null
                || (newValue=src[i].NewValue) == null
                || (oldValue=src[i].OldValue) == null) continue;
            name = validFieldName(name);
            if (name in dict) {
                if (dict[name].NewValue instanceof Array) {
                    dict[name].NewValue.push(newValue);
                    dict[name].OldValue.push(oldValue);
                } else {
                    dict[name].NewValue = [newValue];
                    dict[name].OldValue = [oldValue];
                }
            } else {
                dict[name] = {
                    NewValue: newValue,
                    OldValue: oldValue,
                };
            }
        }
        evt.Put(options.to, dict);
    }
}

function exchangeAdminSchema(debug) {
    var builder = new PipelineBuilder("o365.audit.ExchangeAdmin", debug);
    builder.Add("saveFields", new processor.Convert({
        fields: [
            {from: 'o365audit.OrganizationName', to: 'organization.name'},
            {from: 'o365audit.OriginatingServer', to: 'server.address'},
        ],
        ignore_missing: true,
        fail_on_error: false
    }));
    return builder.Build();
}

function typeMapEnrich(conversions) {
    return function (evt) {
        var action = evt.Get("event.action");
        if (action != null && conversions.hasOwnProperty(action)) {
            var conv = conversions[action];
            if (conv.action !== undefined) evt.Put("event.action", conv.action);
            if (conv.category !== undefined) evt.Put("event.category", conv.category);
            if (conv.type !== undefined) evt.Put("event.type", conv.type);
            var n = conv.copy !== undefined? conv.copy.length : 0;
            for (var i=0; i<n; i++) {
                var value = evt.Get(conv.copy[i].from);
                if (value != null)
                    evt.Put(conv.copy[i].to, value);
            }
        }
    }
}

function azureADSchema(debug) {
    var azureADConversion = {
        'Add user.': {
            action: "added-user-account",
            category: 'iam',
            type: ['user', 'creation'],
            copy: [
                {
                    from: 'o365audit.ObjectId',
                    to: 'user.target.id',
                }
            ],
        },
        'Update user.': {
            action: "modified-user-account",
            category: 'iam',
            type: ['user', 'change'],
            copy: [
                {
                    from: 'o365audit.ObjectId',
                    to: 'user.target.id',
                }
            ],
        },
        'Delete user.': {
            action: "deleted-user-account",
            category: 'iam',
            type: ['user', 'deletion'],
            copy: [
                {
                    from: 'o365audit.ObjectId',
                    to: 'user.target.id',
                }
            ],
        },
    };

    var builder = new PipelineBuilder("o365.audit.AzureActiveDirectory", debug);
    builder.Add("setIAMFields", typeMapEnrich(azureADConversion));
    return builder.Build();
}

function teamsSchema(debug) {
    var teamsConversion = {
        'TeamCreated': {
            action: "added-group-account-to",
            category: 'iam',
            type: ['group', 'creation'],
            copy: [
                {
                    from: 'o365audit.TeamName',
                    to: 'group.name',
                }
            ],
        },
        'MemberAdded': {
            action: "added-users-to-group",
            category: 'iam',
            type: ['group', 'change'],
        },

        'Delete user.': {
            action: "deleted-user-account",
            category: 'iam',
            type: ['user', 'deletion'],
            copy: [
                {
                    from: 'o365audit.ObjectId',
                    to: 'user.target.id',
                }
            ],
        },
    };

    var builder = new PipelineBuilder("o365.audit.MicrosoftTeams", debug);
    builder.Add("setIAMFields", typeMapEnrich(teamsConversion));
    builder.Add("groupMembersToRelatedUser", function (evt) {
        var m = evt.Get("o365audit.Members");
        if (m == null || m.forEach == null) return;
        m.forEach(function (obj) {
            if (obj != null && obj.hasOwnProperty('UPN'))
                evt.AppendTo('related.user', obj.UPN);
        })
    })
    return builder.Build();
}

function azureADLogonSchema(debug) {
    var builder = new PipelineBuilder("o365.audit.AzureActiveDirectoryLogon", debug);
    builder.Add("setEventAuthFields", function(evt){
       evt.Put("event.category", "authentication");
       var outcome = evt.Get("event.outcome");
       // As event.type is an array, this sets both the traditional
       // "authentication_success"/"authentication_failure"
       // and the ECS standard "start".
       var types = ["start"];
       if (outcome != null && outcome !== "unknown") {
           types.push("authentication_" + outcome);
       }
       evt.Put("event.type", types);
    });
    return builder.Build();
}

function sharePointFileOperationSchema(debug) {
    var builder = new PipelineBuilder("o365.audit.SharePointFileOperation", debug);
    builder.Add("saveFields", new processor.Convert({
        fields: [
            {from: 'o365audit.ObjectId', to: 'url.original'},
            {from: 'o365audit.SourceRelativeUrl', to: 'file.directory'},
            {from: 'o365audit.SourceFileName', to: 'file.name'},
            {from: 'o365audit.SourceFileExtension', to: 'file.extension'},
        ],
        ignore_missing: true,
        fail_on_error: false
    }));

    var actionToCategoryType = {
        ComplianceSettingChanged: ['configuration', 'change'],
        FileAccessed: ['file', 'access'],
        FileDeleted: ['file', 'deletion'],
        FileDownloaded: ['file', 'access'],
        FileModified: ['file', 'change'],
        FileMoved:  ['file', 'change'],
        FileRenamed: ['file', 'change'],
        FileRestored: ['file', 'change'],
        FileUploaded: ['file', 'creation'],
        FolderCopied: ['file', 'creation'],
        FolderCreated: ['file', 'creation'],
        FolderDeleted: ['file', 'deletion'],
        FolderModified: ['file', 'change'],
        FolderMoved: ['file', 'change'],
        FolderRenamed: ['file', 'change'],
        FolderRestored: ['file', 'change'],
    };

    builder.Add("setEventFields", function(evt) {
        var action = evt.Get("o365audit.Operation");
        if (action == null) return;
        var fields = actionToCategoryType[action];
        if (fields == null) return;
        evt.Put("event.category", fields[0]);
        evt.Put("event.type", fields[1]);
    });
    return builder.Build();
}

function exchangeMailboxSchema(debug) {
    var builder = new PipelineBuilder("o365.audit.SharePointFileOperation", debug);
    builder.Add("saveFields", new processor.Convert({
        fields: [
            {from: 'o365audit.MailboxOwnerUPN', to: 'user.email'},
            {from: 'o365audit.LogonUserSid', to: 'user.id', type: 'string'},
            {from: 'o365audit.LogonUserDisplayName', to: 'user.full_name'},
            {from: 'o365audit.OrganizationName', to: 'organization.name'},
            {from: 'o365audit.OriginatingServer', to: 'server.address'},
            {from: 'o365audit.ClientIPAddress', to: 'client.address'},
            {from: 'o365audit.ClientProcessName', to: 'process.name'},
        ],
        ignore_missing: true,
        fail_on_error: false
    }));
    return builder.Build();
}

function dataLossPreventionSchema(debug) {
    var builder = new PipelineBuilder("o365.audit.DLP", debug);
    builder.Add("setEventFields", new processor.AddFields({
        target: 'event',
        fields: {
            kind: 'alert',
            category: 'file',
            type: 'access',
        },
    }));

    builder.Add("saveFields", new processor.Convert({
        fields: [
            // SharePoint metadata
            {from: 'o365audit.SharePointMetaData.From', to: 'user.id'},
            {from: 'o365audit.SharePointMetaData.FileName', to: 'file.name'},
            {from: 'o365audit.SharePointMetaData.FilePathUrl', to: 'url.original'},
            {from: 'o365audit.SharePointMetaData.UniqueId', to: 'file.inode'},
            {from: 'o365audit.SharePointMetaData.UniqueID', to: 'file.inode'},
            {from: 'o365audit.SharePointMetaData.FileOwner', to: 'file.owner'},

            // Exchange metadata
            {from: 'o365audit.ExchangeMetaData.From', to: 'source.user.email'},
            {from: 'o365audit.ExchangeMetaData.Subject', to: 'message'},

            // Policy details
            {from: 'o365audit.PolicyId', to: 'rule.id'},
            {from: 'o365audit.PolicyName', to: 'rule.name'},
        ],
        ignore_missing: true,
        fail_on_error: false
    }));

    builder.Add("setMTime", new processor.Timestamp({
        field: "o365audit.SharePointMetaData.LastModifiedTime",
        target_field: "file.mtime",
        layouts: [
            "2006-01-02T15:04:05",
            "2006-01-02T15:04:05Z",
        ],
        ignore_missing: true,
        ignore_failure: true,
    }));

    builder.Add("appendDestinationEmails", function(evt) {
       var list = [];
       var fields = [
           'o365audit.ExchangeMetaData.To',
           'o365audit.ExchangeMetaData.CC',
           'o365audit.ExchangeMetaData.BCC',
       ];
       for (var i=0; i<fields.length; i++) {
           var value = evt.Get(fields[i]);
           if (value == null) continue;
           if (value instanceof Array) {
               list = list.concat(value);
           } else {
               list.push(value);
           }
       }
       if (list.length == 1) {
           evt.Put("destination.user.email", list[0]);
       } else if (list.length > 1) {
           evt.Put("destination.user.email", list);
       }
    });

    // ExceptionInfo is documented as string but has been observed to be an object.
    builder.Add("fixExceptionInfo", function(evt) {
        var key = "o365audit.ExceptionInfo";
        var eInfo = evt.Get(key);
        if (eInfo == null) return;
        if (typeof eInfo === "string") {
            if (eInfo === "") {
                evt.Delete(key);
            } else {
                evt.Put(key, {
                    Reason: eInfo,
                });
            }
        }
    });

    builder.Add("extractRules", function(evt) {
        var policies = evt.Get("o365audit.PolicyDetails");
        if (policies == null) return;
        // rule.id will be an array of all rules' IDs.
        var ruleIds = [];
        // rule.name will be an array of all rules' names.
        var ruleNames = [];
        // event.severity will be the higher severity seen.
        var maxSeverity = -1;
        // event.outcome will determine if access to sensitive data was allowed.
        // Either because the rules were configured to only alert or because
        // the alert was overridden by the user.
        var allowed = true;
        for (var i = 0; i < policies.length; i++) {
            var rules = policies[i].Rules;
            if (rules == null) continue;
            for (var j = 0; j < rules.length; j++) {
                var rule = rules[j];
                var id = rule.RuleId;
                var name = rule.RuleName;
                var sev = severityToCode(rule.Severity);
                if (id != null && name != null) {
                    ruleIds.push(id);
                    ruleNames.push(name);
                }
                if (sev > maxSeverity) maxSeverity = sev;
                if (allowed) {
                    if (rule.Actions != null && rule.Actions.indexOf("BlockAccess") > -1) {
                        allowed = false;
                    }
                }
            }
        }
        if (ruleIds.length === 1) {
            evt.Put("rule.id", ruleIds[0]);
            evt.Put("rule.name", ruleNames[0]);
        } else if (ruleIds.length > 0) {
            evt.Put("rule.id", ruleIds);
            evt.Put("rule.name", ruleNames);
        }
        if (maxSeverity > -1) {
            evt.Put("event.severity", maxSeverity);
        }
        evt.Put("event.outcome", (allowed || isBlockOverride(evt))? "success" : "failure");
    });
    return builder.Build();
}

// Numeric mapping for o365 mgmt API severities.
function severityToCode(str) {
    if (str == null) return -1;
    switch (str.toLowerCase()) {
        case 'informational': return 1; // undocumented severity.
        case 'low': return 2;
        case 'medium': return 3;
        case 'high': return 4;
        default: return -1;
    }
}

// Was a DLP alert overridden with an exception?
function isBlockOverride(evt) {
    switch (evt.Get("o365audit.Operation").toLowerCase()) {
        // Undo means the block was undone via change of policy or override.
        case "dlpruleundo": return true;
        // Info means it was detected as a false positive but no action taken.
        case "dlpinfo": return false;
    }
    // It's not clear to me the format of ExceptionInfo. It could be an object
    // or a string containing a JSON object. Assume that if present, an exception
    // is made.
    var exInfo = evt.Get('o365audit.ExceptionInfo');
    return exInfo != null && exInfo !== "";
}

function yammerSchema(debug) {
    var builder = new PipelineBuilder("o365.audit.Yammer", debug);
    builder.Add("saveFields", new processor.Convert({
        fields: [
            {from: 'o365audit.ActorUserId', to: 'user.email'},
            {from: 'o365audit.ActorYammerUserId', to: 'user.id', type: 'string'},
            {from: 'o365audit.FileId', to:'file.inode'},
            {from: 'o365audit.FileName', to: 'file.name'},
            {from: 'o365audit.GroupName', to: 'group.name'},
            {from: 'o365audit.TargetUserId', to: 'destination.user.email'},
            {from: 'o365audit.TargetYammerUserId', to: 'destination.user.id'},
        ],
        ignore_missing: true,
        fail_on_error: false
    }));

    var yammerConversion = {
        // Network or verified admin changes the Yammer network's configuration.
        // This includes setting the interval for exporting data and enabling chat.
        NetworkConfigurationUpdated: {
            category: "configuration",
            type: "change",
        },
        // Verified admin updates the Yammer network's security configuration.
        // This includes setting password expiration policies and restrictions
        // on IP addresses.
        NetworkSecurityConfigurationUpdated: {
            category: ["iam", "configuration"],
            type: ["admin", "change"],
        },
        // Verified admin updates the setting for the network data retention
        // policy to either Hard Delete or Soft Delete. Only verified admins
        // can perform this operation.
        SoftDeleteSettingsUpdated: {
            category: "configuration",
            type: "change",
        },
        // Network or verified admin changes the information that appears on
        // member profiles for network users network.
        ProcessProfileFields: {
            category: "configuration",
            type: "change"
        },
        // Verified admin turns Private Content Mode on or off. This mode
        // lets an admin view the posts in private groups and view private
        // messages between individual users (or groups of users). Only verified
        // admins only can perform this operation.
        SupervisorAdminToggled: {
            category: "configuration",
            type: "change"
        },
        // User uploads a file.
        FileCreated: {
            category: "file",
            type: "creation"
        },
        // User creates a group.
        GroupCreation: {
            category: "iam",
            type: ["group", "creation"],
        },
        // A group is deleted from Yammer.
        GroupDeletion: {
            category: "iam",
            type: ["group", "deletion"]
        },
        // User downloads a file.
        FileDownloaded: {
            category: "file",
            type: "access"
        },
        // User shares a file with another user.
        FileShared: {
            category: "file",
            type: "access"
        },
        // Network or verified admin suspends (deactivates) a user from Yammer.
        NetworkUserSuspended: {
            category: "iam",
            type: "user"
        },
        // User account is suspended (deactivated).
        UserSuspension: {
            category: "iam",
            type: "user"
        },
        // User changes the description of a file.
        FileUpdateDescription: {
            category: "file",
            type: "access"
        },
        // User changes the name of a file.
        FileUpdateName: {
            category: "file",
            type: "creation",
        },
        // User views a file.
        FileVisited: {
            category: "file",
            type: "access",
        },
    };

    builder.Add("setEventFields", typeMapEnrich(yammerConversion));
    return builder.Build();
}

function securityComplianceAlertsSchema(debug) {
    var builder = new PipelineBuilder("o365.audit.SecurityComplianceAlerts", debug);
    builder.Add("saveFields", new processor.Convert({
        fields: [
            {from: 'o365audit.Comments', to: 'message'},
            {from: 'o365audit.Name', to: 'rule.name'},
            {from: 'o365audit.PolicyId', to: 'rule.id'},
            {from: 'o365audit.Category', to: 'rule.category'},
            {from: 'o365audit.EntityType', to: 'rule.ruleset'},
            // This contains the entity that triggered the alert.
            // Name of a malware or email address.
            // Need to find a better ECS field for it.
            {from: 'o365audit.AlertEntityId', to: 'rule.description'},
            {from: 'o365audit.AlertLinks', to: 'rule.reference'},
        ],
        ignore_missing: true,
        fail_on_error: false
    }));
    builder.Add("setEventFields", new processor.AddFields({
        target: 'event',
        fields: {
            kind: 'alert',
            category: 'web',
            type: 'info',
        },
    }));
    // event.severity is numeric.
    builder.Add("mapSeverity", function(evt) {
       var sev = severityToCode(evt.Get("o365audit.Severity"));
       if (sev >= 0) {
           evt.Put("event.severity", sev);
       }
    });
    builder.Add("mapCategory", makeMapper({
        from: 'o365audit.Category',
        to: 'event.category',
        default: 'authentication',
        lowercase: true,
        mappings: {
            'accessgovernance': 'authentication',
            'datagovernance': 'file',
            'datalossprevention': 'file',
            'threatmanagement': 'malware',
        },
    }));
    builder.Add("saveEntity", makeConditional({
        condition: function(evt) {
            return evt.Get("o365audit.EntityType");
        },
        'User': new processor.Convert({
            fields: [
                {from: "o365audit.AlertEntityId", to: "user.id", type: 'string'},
            ],
            ignore_missing: true,
            fail_on_error: false
        }),
        'Recipients': new processor.Convert({
            fields: [
                {from: "o365audit.AlertEntityId", to: "user.email"},
            ],
            ignore_missing: true,
            fail_on_error: false
        }),
        'Sender': new processor.Convert({
            fields: [
                {from: "o365audit.AlertEntityId", to: "user.email"},
            ],
            ignore_missing: true,
            fail_on_error: false
        }),
        'MalwareFamily': new processor.Convert({
            fields: [
                {from: "o365audit.AlertEntityId", to: "threat.technique.id"},
            ],
            ignore_missing: true,
            fail_on_error: false
        }),
    }));
    return builder.Build();
}

function splitEmailUserID(prefix) {
    var idField = prefix + ".id",
        nameField = prefix + ".name",
        domainField = prefix + ".domain",
        emailField = prefix + ".email";
    return function(evt) {
        var email = evt.Get(idField);
        if (email == null) return;
        var pos = email.indexOf('@');
        if (pos === -1) return;
        evt.Put(emailField, email);
        evt.Put(nameField, email.substr(0, pos));
        evt.Put(domainField, email.substr(pos+1));
    }
}

function AuditProcessor(tenant_names, debug) {
    var builder = new PipelineBuilder("o365.audit", debug);

    var unsetIPValues = {"null": true, "<null>": true, "": true};
    builder.Add("cleanupNulls", function(event) {
        [
            "o365audit.ClientIP",
            "o365audit.ClientIPAddress",
            "o365audit.ActorIpAddress",
            "o365audit.OriginatingServer"
        ].forEach(function(field) {
            if (event.Get(field) in unsetIPValues) event.Delete(field);
        });
    });
    builder.Add("convertCommonAuditRecordFields", new processor.Convert({
        fields: [
            {from: "o365audit.Id", to: "event.id"},
            {from: "o365audit.ClientIP", to: "client.address"},
            {from: "o365audit.ClientIPAddress", to: "client.address"},
            {from: "o365audit.ActorIpAddress", to: "client.address"},
            {from: "o365audit.UserId", to: "user.id", type: "string"},
            {from: "o365audit.Workload", to: "event.provider"},
            {from: "o365audit.Operation", to: "event.action"},
            {from: "o365audit.OrganizationId", to: "organization.id"},
            // Extra common fields:
            {from: "o365audit.UserAgent", to: "user_agent.original"},
        ],
        ignore_missing: true,
        fail_on_error: false
    }));
    builder.Add("mapEventType", makeMapper({
        from: 'o365audit.RecordType',
        to: 'event.code',
        // Keep original RecordType for unknown mappings.
        default: function(recordType) {
            return recordType;
        },
        mappings: {
            1: 'ExchangeAdmin', // Events from the Exchange admin audit log.
            2: 'ExchangeItem', // Events from an Exchange mailbox audit log for actions that are performed on a single item, such as creating or receiving an email message.
            3: 'ExchangeItemGroup', // Events from an Exchange mailbox audit log for actions that can be performed on multiple items, such as moving or deleted one or more email messages.
            4: 'SharePoint', // SharePoint events.
            6: 'SharePointFileOperation', // SharePoint file operation events.
            8: 'AzureActiveDirectory', // Azure Active Directory events.
            7: 'OneDrive', // OneDrive for Business events.
            9: 'AzureActiveDirectoryAccountLogon', // Azure Active Directory OrgId logon events (deprecating).
            10: 'DataCenterSecurityCmdlet', // Data Center security cmdlet events.
            11: 'ComplianceDLPSharePoint', // Data loss protection (DLP) events in SharePoint and OneDrive for Business.
            12: 'Sway', // Events from the Sway service and clients.
            13: 'ComplianceDLPExchange', // Data loss protection (DLP) events in Exchange, when configured via Unified DLP Policy. DLP events based on Exchange Transport Rules are not supported.
            14: 'SharePointSharingOperation', // SharePoint sharing events.
            15: 'AzureActiveDirectoryStsLogon', // Secure Token Service (STS) logon events in Azure Active Directory.
            16: 'SkypeForBusinessPSTNUsage', // Public Switched Telephone Network (PSTN) events from Skype for Business.
            17: 'SkypeForBusinessUsersBlocked', // Blocked user events from Skype for Business.
            18: 'SecurityComplianceCenterEOPCmdlet', // Admin actions from the Security & Compliance Center.
            19: 'ExchangeAggregatedOperation', // Aggregated Exchange mailbox auditing events.
            20: 'PowerBIAudit', // Power BI events.
            21: 'CRM', // Microsoft CRM events.
            22: 'Yammer', // Yammer events.
            23: 'SkypeForBusinessCmdlets', // Skype for Business events.
            24: 'Discovery', // Events for eDiscovery activities performed by running content searches and managing eDiscovery cases in the Security & Compliance Center.
            25: 'MicrosoftTeams', // Events from Microsoft Teams.
            28: 'ThreatIntelligence', // Phishing and malware events from Exchange Online Protection and Office 365 Advanced Threat Protection.
            29: 'MailSubmission', // Submission events from Exchange Online Protection and Microsoft Defender for Office 365.
            30: 'MicrosoftFlow', // Microsoft Power Automate (formerly called Microsoft Flow) events.
            31: 'AeD', // Advanced eDiscovery events.
            32: 'MicrosoftStream', // Microsoft Stream events.
            33: 'ComplianceDLPSharePointClassification', // Events related to DLP classification in SharePoint.
            34: 'ThreatFinder', // Campaign-related events from Microsoft Defender for Office 365.
            35: 'Project', // Microsoft Project events.
            36: 'SharePointListOperation', // SharePoint List events.
            37: 'SharePointCommentOperation', // SharePoint comment events.
            38: 'DataGovernance', // Events related to retention policies and retention labels in the Security & Compliance Center
            39: 'Kaizala', // Kaizala events.
            40: 'SecurityComplianceAlerts', // Security and compliance alert signals.
            41: 'ThreatIntelligenceUrl', // Safe links time-of-block and block override events from Office 365 Advanced Threat Protection.
            42: 'SecurityComplianceInsights', // Events related to insights and reports in the Office 365 security and compliance center.
            43: 'MIPLabel', // Events related to the detection in the Transport pipeline of email messages that have been tagged (manually or automatically) with sensitivity labels.
            44: 'WorkplaceAnalytics', // Workplace Analytics events.
            45: 'PowerAppsApp', // Power Apps events.
            46: 'PowerAppsPlan', // Subscription plan events for Power Apps.
            47: 'ThreatIntelligenceAtpContent', // Phishing and malware events for files in SharePoint, OneDrive for Business, and Microsoft Teams from Office 365 Advanced Threat Protection.
            48: 'LabelContentExplorer', // Events related to data classification content explorer.
            49: 'TeamsHealthcare', // Events related to the Patients application in Microsoft Teams for Healthcare.
            50: 'ExchangeItemAggregated', // Events related to the MailItemsAccessed mailbox auditing action.
            51: 'HygieneEvent', // Events related to outbound spam protection.
            52: 'DataInsightsRestApiAudit', // Data Insights REST API events.
            53: 'InformationBarrierPolicyApplication', // Events related to the application of information barrier policies.
            54: 'SharePointListItemOperation', // SharePoint list item events.
            55: 'SharePointContentTypeOperation', // SharePoint list content type events.
            56: 'SharePointFieldOperation', // SharePoint list field events.
            57: 'MicrosoftTeamsAdmin', // Teams admin events.
            58: 'HRSignal', // Events related to HR data signals that support the Insider risk management solution.
            59: 'MicrosoftTeamsDevice', // Teams device events.
            60: 'MicrosoftTeamsAnalytics', // Teams analytics events.
            61: 'InformationWorkerProtection', // Events related to compromised user alerts.
            62: 'Campaign', // Email campaign events from Microsoft Defender for Office 365.
            63: 'DLPEndpoint', // Endpoint DLP events.
            64: 'AirInvestigation', // Automated incident response (AIR) events.
            65: 'Quarantine', // Quarantine events.
            66: 'MicrosoftForms', // Microsoft Forms events.
            67: 'ApplicationAudit', // Application audit events.
            68: 'ComplianceSupervisionExchange', // Events tracked by the Communication compliance offensive language model.
            69: 'CustomerKeyServiceEncryption', // Events related to the customer key encryption service.
            70: 'OfficeNative', // Events related to sensitivity labels applied to Office documents.
            71: 'MipAutoLabelSharePointItem', // Auto-labeling events in SharePoint.
            72: 'MipAutoLabelSharePointPolicyLocation', // Auto-labeling policy events in SharePoint.
            73: 'MicrosoftTeamsShifts', // Teams Shifts events.
            75: 'MipAutoLabelExchangeItem', // Auto-labeling events in Exchange.
            76: 'CortanaBriefing', // Briefing email events.
            78: 'WDATPAlerts', // Events related to alerts generated by Windows Defender for Endpoint.
            82: 'SensitivityLabelPolicyMatch', // Events generated when the file labeled with a sensitivity label is opened or renamed.
            83: 'SensitivityLabelAction', // Event generated when sensitivity labels are applied, updated, or removed from a file.
            84: 'SensitivityLabeledFileAction', // Events generated when a file labeled with a sensitivity label is opened or renamed.
            85: 'AttackSim', // Attack simulator events.
            86: 'AirManualInvestigation', // Events related to manual investigations in Automated investigation and response (AIR).
            87: 'SecurityComplianceRBAC', // Security and compliance RBAC events.
            88: 'UserTraining', // Attack simulator training events in Microsoft Defender for Office 365.
            89: 'AirAdminActionInvestigation', // Events related to admin actions in Automated investigation and response (AIR).
            90: 'MSTIC', // Threat intelligence events in Microsoft Defender for Office 365.
            91: 'PhysicalBadgingSignal', // Events related to physical badging signals that support the Insider risk management solution.
            93: 'AipDiscover', // Azure Information Protection (AIP) scanner events.
            94: 'AipSensitivityLabelAction', // AIP sensitivity label events.
            95: 'AipProtectionAction', // AIP protection events.
            96: 'AipFileDeleted', // AIP file deletion events.
            97: 'AipHeartBeat', // AIP heartbeat events.
            98: 'MCASAlerts', // Events corresponding to alerts triggered by Microsoft Cloud App Security.
            99: 'OnPremisesFileShareScannerDlp', // Events related to scanning for sensitive data on file shares.
            100: 'OnPremisesSharePointScannerDlp', // Events related to scanning for sensitive data in SharePoint.
            101: 'ExchangeSearch', // Events related to using Outlook on the web (OWA) to search for mailbox items.
            102: 'SharePointSearch', // Events related to searching an organization's SharePoint home site.
            103: 'PrivacyInsights', // Privacy insight events.
            105: 'MyAnalyticsSettings', // MyAnalytics events.
            106: 'SecurityComplianceUserChange', // Events related to modifying or deleting a user.
            107: 'ComplianceDLPExchangeClassification', // Exchange DLP classification events.
            109: 'MipExactDataMatch', // Exact Data Match (EDM) classification events.
            113: 'MS365DCustomDetection', // Events related to custom detection actions in Microsoft 365 Defender.
            147: 'CoreReportingSettings', // Reports settings events.
            148: 'ComplianceConnector', // Events related to importing non-Microsoft data using data connectors in the Microsoft Purview compliance portal.
            174: 'DataShareOperation', // Events related to sharing of data ingested via SystemSync.
            181: 'EduDataLakeDownloadOperation', // Events related to the export of SystemSync ingested data from the lake.
        },
    }));

    builder.Add("setEventFields", new processor.AddFields({
        target: 'event',
        fields: {
            kind: 'event',
            type: 'info',
            // Not so sure about web as a default category:
            category: 'web',
        },
    }));

    builder.Add("mapEventOutcome", makeMapper({
        from: 'o365audit.ResultStatus',
        to: 'event.outcome',
        lowercase: true,
        default: 'success',
        mappings: {
            'success': 'success', // This one is necessary to map Success
            'succeeded': 'success',
            'partiallysucceeded': 'success',
            'true': 'success',
            'failed': 'failure',
            'false': 'failure',
        },
    }));

    builder.Add("makeParametersDict", makeObjFromNameValuePairArray({
        from: 'o365audit.Parameters',
        to: 'o365audit.Parameters',
    }));

    builder.Add("makeExtendedPropertiesDict", makeObjFromNameValuePairArray({
        from: 'o365audit.ExtendedProperties',
        to: 'o365audit.ExtendedProperties',
    }));

    builder.Add("makeModifiedPropertyDict", makeDictFromModifiedPropertyArray({
        from: 'o365audit.ModifiedProperties',
        to: 'o365audit.ModifiedProperties',
    }));

    // Turn AlertLinks into an array of keyword instead of array of objects.
    builder.Add("alertLinks", function (evt) {
        var list = evt.Get("o365audit.AlertLinks");
        if (list == null || !(list instanceof Array)) return;
        var links = [];
        for (var i=0; i<list.length; i++) {
            var link = list[i].AlertLinkHref;
            if (link != null && typeof link === "string" && link.length > 0) {
                links.push(link);
            }
        }
        switch (links.length) {
            case 0:
                evt.Delete('o365audit.AlertLinks');
                break;
            case 1:
                evt.Put("o365audit.AlertLinks", links[0]);
                break;
            default:
                evt.Put("o365audit.AlertLinks", links);
        }
    });

    // Populate event specific fields.
    var dlp = dataLossPreventionSchema(debug);
    builder.Add("productSpecific", makeConditional({
        condition: function(event) {
            return event.Get("event.code");
        },
        'ExchangeAdmin': exchangeAdminSchema(debug).Run,
        'ExchangeItem': exchangeMailboxSchema(debug).Run,
        'AzureActiveDirectory': azureADSchema(debug).Run,
        'AzureActiveDirectoryStsLogon': azureADLogonSchema(debug).Run,
        'SharePointFileOperation': sharePointFileOperationSchema(debug).Run,
        'SecurityComplianceAlerts': securityComplianceAlertsSchema(debug).Run,
        'ComplianceDLPSharePoint': dlp.Run,
        'ComplianceDLPExchange': dlp.Run,
        'Yammer': yammerSchema(debug).Run,
        'MicrosoftTeams': teamsSchema(debug).Run,
    }));

    builder.Add("extractClientIPPortBrackets", new processor.Dissect({
        tokenizer: '[%{_ip}]:%{_port}',
        field: 'client.address',
        target_prefix: 'client',
        'when.and': [
            {'not.has_fields': ['client._ip', 'client._port']},
            {'contains.client.address': ']:'},
        ],
    }));
    builder.Add("extractClientIPv4Port", new processor.Dissect({
        tokenizer: '%{_ip}:%{_port}',
        field: 'client.address',
        target_prefix: 'client',
        'when.and': [
            {'not.has_fields': ['client._ip', 'client._port']},
            {'contains.client.address': '.'},
            {'contains.client.address': ':'},
            // Best effort to avoid parsing IPv6-mapped IPv4 as ip:port.
            // Won't succeed if IPv6 address is not shortened.
            {'not.contains.client.address': '::'},
        ],
    }));

    // Copy the client/server.address to .ip fields if they are valid IPs.
    builder.Add("convertIPs", new processor.Convert({
        fields: [
            {from: "client.address", to: "client.ip", type: "ip"},
            {from: "server.address", to: "server.ip", type: "ip"},
            {from: "client._ip",     to: "client.ip", type: "ip"},
            {from: "client._port",   to: "client.port", type: "long"},
        ],
        ignore_missing: true,
        fail_on_error: false
    }));
    builder.Add("removeTempIP", function (evt) {
        evt.Delete("client._ip");
        evt.Delete("client._port");
    });
    builder.Add("setSrcDstFields", new processor.Convert({
        fields: [
            {from: "client.ip", to: "source.ip"},
            {from: "client.port", to: "source.port"},
            {from: "server.ip", to: "destination.ip"},
        ],
        ignore_missing: true,
        fail_on_error: false
    }));

    [
      'user',
      'user.target',
      'source.user',
      'destination.user',
    ].forEach(function (prefix) {
        builder.Add('setFromID' + prefix, splitEmailUserID(prefix));
    })

    builder.Add("setNetworkType", function(event) {
        var ip = event.Get("client.ip");
        if (ip == null) return;
        event.Put("network.type", ip.indexOf(".") !== -1? "ipv4" : "ipv6");
    });

    builder.Add("setRelatedIP", appendFields({
        fields: [
            "client.ip",
            "server.ip",
        ],
        to: 'related.ip'
    }));

    builder.Add("setRelatedUser", appendFields({
        fields: [
            "user.name",
            "user.target.name",
            "file.owner",
        ],
        to: 'related.user'
    }));

    // Set user-agent from an alternative location.
    builder.Add("altUserAgent", function(evt) {
        var ext = evt.Get("o365audit.ExtendedProperties.UserAgent");
        if (ext != null) evt.Put("user_agent.original", ext);
    });

    // Set host.name to the O365 tenant. This is necessary to aggregate events
    // in SIEM app based on the tenant instead of the host where Filebeat is
    // running.
    builder.Add("setHostName", function(evt) {
        var value;
        if ((value=evt.Get("organization.id"))!=null) {
            value = value.toLowerCase();
            evt.Put("host.id", value);
            // Use tenant name provided in the configuration.
            if (value in tenant_names && value !== "") {
                evt.Put("organization.name", value);
                evt.Put("host.name", tenant_names[value]);
                return;
            }
        }
        if ((value=evt.Get("organization.name"))!=null ||
            (value=evt.Get("user.domain")) != null ) {
            evt.Put("host.name", value);
        }
    });

    builder.Add("saveRaw", new processor.Convert({
        fields: [
            {from: "o365audit", to: "o365.audit"},
        ],
        mode: "rename"
    }));

    var chain = builder.Build();
    return {
        process: chain.Run
    };
}


var audit;

// Register params from configuration.
function register(params) {
    var tenant_names = {};
    if (params.tenants != null) {
        for (var i = 0; i < params.tenants.length; i++) {
            tenant_names[params.tenants[i].id] = params.tenants[i].name.toLowerCase();
        }
    }
    audit = new AuditProcessor(tenant_names, params.debug);
}

function process(evt) {
    return audit.process(evt);
}