From a709dbcaef70e07206c23b2b209998aff5ff176e Mon Sep 17 00:00:00 2001 From: Tobias Kurze Date: Thu, 28 Nov 2019 19:39:53 +0100 Subject: [PATCH] added permissions api and websocket stuff --- Pipfile | 4 + backend/__main__.py | 7 +- backend/api/__init__.py | 3 + backend/api/models.py | 4 +- backend/api/permission_api.py | 91 ++++++++++++++++++ backend/api/user_api.py | 2 +- backend/config.py | Bin 4409 -> 4481 bytes backend/cron/__init__.py | 73 +++++++++++++- backend/models/user_model.py | 16 +++ backend/tools/simple_state_checker.py | 11 +++ backend/websocket/base.py | 18 ++-- backend/websocket/handlers.py | 0 ...cket_base.py => websocket_base_testing.py} | 0 backend/websocket/websocket_test_client.py | 24 +++-- 14 files changed, 229 insertions(+), 24 deletions(-) create mode 100644 backend/api/permission_api.py create mode 100644 backend/websocket/handlers.py rename backend/websocket/{websocket_base.py => websocket_base_testing.py} (100%) diff --git a/Pipfile b/Pipfile index f771728..5381d84 100644 --- a/Pipfile +++ b/Pipfile @@ -31,6 +31,10 @@ ics = "*" coloredlogs = "*" pythonping = "*" scapy = "*" +python-socketioclient = "*" +python-socketio = {version = "*",extras = ["client"]} +socketio-client = "*" +websocket-client = "*" [dev-packages] diff --git a/backend/__main__.py b/backend/__main__.py index 95e5a1e..761b776 100644 --- a/backend/__main__.py +++ b/backend/__main__.py @@ -11,6 +11,7 @@ from backend import app, db from backend.models import room_model, recorder_model, RecorderCommand from backend.recorder_adapters import get_defined_recorder_adapters from backend.tools.model_updater import update_recorder_models_database, create_default_recorders +from backend.websocket.base import WebSocketBase def main(): @@ -37,7 +38,11 @@ def main(): except Exception as e: logging.critical(e) - app.run(debug=True, host="0.0.0.0") + wsb = WebSocketBase() + print("running websocket...(replaces normal app.run()") + wsb.start_websocket(debug=True) + # print("running web app...") + #app.run(debug=True, host="0.0.0.0", threaded=True) if __name__ == '__main__': diff --git a/backend/api/__init__.py b/backend/api/__init__.py index 4c441e1..fe4a2d1 100644 --- a/backend/api/__init__.py +++ b/backend/api/__init__.py @@ -27,6 +27,7 @@ api_v1 = Api(api_bp, prefix="/v1", version='0.1', title='Vue Test API', api_user = Namespace('user', description="User management namespace", authorizations=api_authorizations) api_group = Namespace('group', description="Group management namespace", authorizations=api_authorizations) +api_permissions = Namespace('permissions', description="Permissions management namespace", authorizations=api_authorizations) api_room = Namespace('room', description="Room management namespace", authorizations=api_authorizations) api_recorder = Namespace('recorder', description="Recorder management namespace", authorizations=api_authorizations) api_virtual_command = Namespace('virtual_command', description="Virtual command namespace", @@ -38,6 +39,7 @@ api_control = Namespace('control', description="Control namespace", api_v1.add_namespace(api_user) api_v1.add_namespace(api_group) +api_v1.add_namespace(api_permissions) api_v1.add_namespace(api_room) api_v1.add_namespace(api_recorder) api_v1.add_namespace(api_virtual_command) @@ -58,6 +60,7 @@ auth_api_v1.add_namespace(auth_api_register_ns) from .example_api import * from .auth_api import * from .user_api import * +from .permission_api import * from .group_api import * from .room_api import * from .recorder_api import * diff --git a/backend/api/models.py b/backend/api/models.py index 2e89c12..0c9fb97 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -20,7 +20,9 @@ user_model = api_user.model('User', { fields.Nested(api_user.model('user_group', {'id': fields.Integer(), 'name': fields.String()})), required=False, description='Group memberships.'), 'favorite_recorders': fields.List( - fields.Nested(api_user.model('favorite_recorder', {'id': fields.Integer(), 'name': fields.String()})), + fields.Nested(api_user.model('favorite_recorder', + {'id': fields.Integer(), 'name': fields.String(), 'offline': fields.Boolean(), + 'created_at': fields.DateTime(), 'last_time_modified': fields.DateTime()})), required=False, description='Favorite recorders.'), }) diff --git a/backend/api/permission_api.py b/backend/api/permission_api.py new file mode 100644 index 0000000..941a399 --- /dev/null +++ b/backend/api/permission_api.py @@ -0,0 +1,91 @@ +# Copyright (c) 2019. Tobias Kurze +""" +This module provides functions related to authentication through the API. +For example: listing of available auth providers or registration of users. + +Login through API does not start a new session, but instead returns JWT. +""" +from flask_jwt_extended import jwt_required +from flask_restplus import fields, Resource + +from backend import db +from backend.api import api_permissions +from backend.models.user_model import Permission + +permission_model = api_permissions.model('Permission', { + 'id': fields.String(required=False, description='The permission\'s identifier'), + 'name': fields.String(required=True, description='The permission\'s name'), + 'description': fields.String(required=False, description='The permission\'s description'), + 'groups': fields.List(fields.Nested(api_permissions.model('group_member', + {'id': fields.Integer(), + 'name': fields.String(), + 'description': fields.String()})), + required=False, description='Groups having the permission.'), + 'access_control_entry': fields.Nested(api_permissions.model('group_member', + {'id': fields.Integer(), + 'name': fields.String(), + 'url': fields.String()}), + required=False, description="Access Control Entry"), +}) + + +@api_permissions.route('/') +@api_permissions.response(404, 'permission not found') +@api_permissions.param('id', 'The permission identifier') +class PermissionResource(Resource): + @jwt_required + @api_permissions.doc('get_permission') + @api_permissions.marshal_with(permission_model) + def get(self, id): + """Fetch a user given its identifier""" + permission = Permission.get_by_id(id) + if permission is not None: + return permission + api_permissions.abort(404) + + @jwt_required + @api_permissions.doc('delete_permission') + @api_permissions.response(204, 'permission deleted') + def delete(self, id): + """Delete a permission given its identifier""" + permission = Permission.get_by_id(id) + if permission is not None: + permission.delete() + return '', 204 + api_permissions.abort(404) + + @jwt_required + @api_permissions.doc('update_permission') + @api_permissions.expect(permission_model) + @api_permissions.marshal_with(permission_model) + def put(self, id): + """Update a task given its identifier""" + permission = Permission.get_by_id(id) + if permission is not None: + permission.name = api_permissions["name"] + db.session.commit() + return permission + api_permissions.abort(404) + + +@api_permissions.route('') +class PermissionList(Resource): + @jwt_required + @api_permissions.doc('permissions') + @api_permissions.marshal_list_with(permission_model) + def get(self): + """ + List all permissions + :return: permissions + """ + return Permission.get_all() + + @jwt_required + @api_permissions.doc('create_permission') + @api_permissions.expect(permission_model) + @api_permissions.marshal_with(permission_model, code=201) + def post(self): + permission = Permission(**api_permissions.payload) + db.session.add(permission) + db.session.commit() + return permission diff --git a/backend/api/user_api.py b/backend/api/user_api.py index af2834b..ec2a868 100644 --- a/backend/api/user_api.py +++ b/backend/api/user_api.py @@ -70,7 +70,7 @@ class UserFavoriteRecorders(Resource): args = generic_id_parser.parse_args() current_user_id = get_jwt_identity() user = User.get_by_identifier(current_user_id) - print(user) + print(args) recorder = Recorder.get_by_identifier(args["id"]) print(recorder) if recorder is None: diff --git a/backend/config.py b/backend/config.py index 1286c24d55454b297be7ae7efa497eab43005177..e64260172f65c556039aca2ab0fa1f7e2a7f2600 100644 GIT binary patch literal 4481 zcmV-{5q|CfM@dveQdv+`02>R)K-Gz=ubMLuk3Oi_f}h7owLVS$0tqz+AOYJm6Z%Ne zwXwIa;P-$d!0gX(KazWk0y zT3ve_8obvhPF`lzy`$Dg%jQd}rLOYf-=A24uOzH>hAI*9z&PXUHQh@s)b9}S6LBmP zp=|f4+Q^#r&%!SKskfsz{M!Mm?PwwfW!g_k=qq}Aj4Nq5D4KM`+o+`=vmQh+CXBWa z%|614ndQwFd|$rGf~Ln0eK^fTkVsKQfPK&2fL#OU>H`THN!zNyo5W@2;qXt;fVh@) zTESkeQ0se^Xzc3K2od#I&&adz_{F7Mu)5_wSk^PqLbFA8QSv<0-?zPm?*9xxukRCuH&?!&bsjtMF$+6-zr+=Z+X9RlWD%MxR> z>ejZ7JE!tca?~P9oRP4ed_aL1)|;I@6Dkx8g%q}NQfG83)~*Jm83f;b6Wzm`iCXeG zC=)tyw2XI*XUGGtK_}-_dzx-yW0aG*9hJjyW={PdCrRKo z9w&TdM^=16+K>LZ@j;uqE-#zxP(8%7muQQeM5?=->&8F~>cx`o&{-;Y5-U9fi`aD&58T-h|8+wh zDM7aOLh_!!8W@YbSr3FukUaLq(lFE@=5jfC0Ue{) z*B&;p79#M^8H*NY4 z9IWT;+?ul3Mz0dW;jNraNNVM4xWeFtD&qUMu!{3;eA*K84cE%>(U$lr##|#fGm-d` zApEZXLUX~SlQJu(q9IJsU0S-_m}@9X{C+9Ia35`6Qr$_T;NYCvQWh9MSpe7#K?DXt z0`zS(@q>grd;f;>8F1^L3WBvPJ7dZ}Yo<4hN8UYsno!i|bO>N62yV>SxNP?eLEK7c z@y+%)j~~lw*aR!)wUaA(W3dXmt6LW6zI~E2f)rMlet?c<6jeK-SYW8t-@`TFx-v z%{*d-Ae<$8(qwl-@}{osRcVE(jt%8`cML$cVU&(GC}YcN&>s_yO8u+OK|su;S!ZD# zn?W>*vf`?0#ydHX$?a%sqxigYF}!>7e@5>`Xgn>kN7!t`gHmtwIV)sMg)?1-soCbBneJ2+I(HXd&Or-vh?p2y1pasiEc1oioC zTTdI6YwLhbc?aRZ4|1ZqHKQ93GRv1?({HJ1OF~?RO||^|&tnh3n?vvO!@3K-KCTB? z&`DN-J8s>*4VSx24}Z3JmXLC|iH{@;9ueeH553zH#lmRF2E-NvassSokX$s8eurct zg=*CL>-r|}b#872-qD$p#h4>p3yZq&poof;MTb_bY(_{0TS?at+=@1%j%T0l{=RTd zg8@psc;19#0mU#-zhCjo3|X)lj!QtM1Ebmw%3-i2`AYg0X~Hro`XZats+9&uq_djS ze@H^^#7((B#&lVdq&0J->_m!`bT;>@Vt^kn*y)5wqKD!PIPmCo3!91NUL^n}yxVJ_co#)f{gcJI z4RY{DzkT?s^H`b{Im;#TNU#moKZpvh<3VXhH5xOiEZUB|MWAM#9(vB zB|H3BjQ0mfS;R5%Bul6zz1{i69>){VNhv#AU|W#$Swtnw5%LU2PHy0EG2@4W?W(UZ zUf;sotlk!5X`a`=2=2ID0HQ0dvkS8j|-v9*GfTkh- zBt|s(7EScZeO}7OVZ;;s-`1a0x@oe{zx30UfEY>7LjjOdUt&dP3 zGsZ(+4>=Y#IPCYI`z4rHnemS!qt?0?;E6|tmE=BiWW-em4ZhmK>INFQL=kobLn!9d zrOxo^>g4=ba`faCddA2->CYyPfD#8}|FiuM&0?F?iI-v8JBIFlFK2uJrJ~}}RLp%f(C%j|`YeIP(Z2VRoxovHpS}n!iGAyy8GO^)MdlgE)^HY&%{9+45#NOT5rtie)xe zOL*t3Op76Ppgh-raPnGj9mVkZq#aio9Z|Z?VOJBG*ALSI1t^tL^nS86498*OA~9_e z@K=Q(t|U!0&*L007NtBHZ?y9}%i_nd6(d@@17#G|l@?*O8qLDE&`bkuatFjoA$)L) z1AK379Nm)5=aR7lvuxSPPHNtdx4X2gx$-&aj)c_wFbsF6oJwn{r9}}&sLwDig*~(B z<3YM0CTkLPQ$pT8l!@7L97s5sIeA$Im$e8AQPh;yGrWtId<@}-8O^#}JZpkoXpUur zV9a};1oA9Sf|sp~&oY3VqYgBA=u&WW#fXgG)jMFCMx^jR&%}Vb^X&Gcx+jjs;p`6R zl;-VfbdJc*Mait))`W+cdzyh>G_qa$UEA>^h|$8IenDhWhO?I{AML&J|1R4R*D&`{ zC1=@}^rc+;Hb|0bM?zTi=d@ebk%~E1u@_!~zIzbcJ9I*wVqmK_AYtnRL%^`3J@;C; zUCLY33PkiQMNg7`*@zmDa);doDUi(MO3 z)wY|B6XG%!b*0^EF{2ukEGWyDb6L-z<^DCIoEfBsN}jxTEfB`mIHdnjKF#AI#x9j| zY+XOMlyPy(qU~#lzp{5=_kTxF`UnmMvx-9T+cR}(n~EEh1MU4NPojQl)qwkskr!yV zB8V6kkXajwo3uF`$>+`&C!B8(XP*72!}rwJn>K?XSr$O$dk3&dld1Q@Z6yRqGb=i< z<8yrKw*0_3ca@|y;i5HI)CWPu;bwD=M7{e6nhqvTf_x>@AvMQotT))-br>t23qa(c z9S=|rAJW}J+><}$6Ul~yH7ZvtRZyc^Co$!Jjk2hd(Hz?2l_U%xJlpvm=W`_i3fx(G zt?d>A!@?o1UfDgVR9nVnATDg)Djhc-QA9P2v z>cx!JqRq;6FuddnR24@4%I>mTYsYMKP2@}KRNT!hx#Tx?h}Ik2TAthSD<9I~_m6*-J__OROlyX<+!oOrgr6Lk{whCNS-46j+d(!c>yI!HUS>i8Ex1 z2EM!$o2W*_@Dg_$@&v534HTGKHZen*-qt!dX|3@t(k$Lq+!&Xp_XQLuMgKq*O=jp; z2G%%FC8A7>?&lw9;g4sh4(j0?)<0)>K3pDEG-G!aN|GO;_H#KfJ2PvRfcT|nqXm5` zOEeVl^J*vUA(u5e6mtWK;PdpgAF?wU?l|`2y0qSon`;5x&wjb+%kLR+EjsxEP?-ul9ns5I~nWr6gsAZ3U-UhwUhVStbj#%A{V9j zfD-~5U$hOxr>Ez}Abz8jPSsKJMY9H5WkgUBEHo?Huv?6dgfVz1w{swQGB!bxNyE*NR(~DE zDM|h1d305x%PYKLbkO{}*@|SWWOF;F%mNh4t}A!g*(*KU2TtkTNYJP!2>>9<0Cg?# z&Imt{(_rqPfBM|)?UWTZ(hj(31tb%J%OxgU2gkdRlWaVKf+i%*C77s_aXCWhUn?_& z+DI#EpuH>**$rUVV6l*=9oq<)50u5A2JPDnUqQ=77O?rcwe^DnU`>4@PH)%{nmc;> zpKm?qGVn5#eu$uexe&WTFXCz)r9JoQAjCFC(?*^;4n}yPPTB9nh?rIy#FBf`ktC)iV- z47P$D0WTV^Gz$xK;Xk1R&-%2{##(%8^S0~Z3`Bd3)h?Ka*#?U(R}2WxOGi-#l=?tR zoPA^endjcZ=j?v^Iej#yed0|mahLxxb~FBxGD`zWpioS==qiVGZ~d2Y4!o`93j4so zSq*Y@RAgh~#N^6Xf{AIav$k=vWS>sc)Ml0_1EK46cssBjx}~SoXZ$t{ajhWVE_p|y z4lF~v7nbl|-u7=>M~3QK&IL-}$61zh!n~>WGKQJ4eUwXXmzc4O|C*$@6U4~}JNQ#X z4VgI^FM9F|QOwk{ka?Xl3b4C&X7i3GK=Z~zcb(*tmAbOis{)u z`|ULOhPpZs&<)A?8_8afJHtcVPf`HN@()ZaS zOF%rPG4__JJ2AGIT>?y>eKb)aE8a;I#>Rp@s|=%gtt3UclRrn+fT#j3=a008WaPI- zTyR^!HINx#y(qWuHJL43vIu3fvLVB;N_8$9t#u{ou5sVWCdTq=Pc+>vQxc$@Elv@U z3(%D8r5WAfJU&a*+JWcJ%01@SYvVlVn4Zdp7DPHTqk?jlZ~3)N(*! z!O*=~^8q{tVLPqQqM9!WmMfXGYl~lGdSFxreVs*ufRg7q3ewqL>P6fW)PcaT*$!a; zjebP!fd4!nr!nar4)pbAp}N|IxQoxum?m_;3nc!%OyjB4@*%v&s$5PR3)W?~;LKoFY7VMdCZm?|0ss4!hy4;T55t zPPrPWGo{P%juu*ljZ2oP?5vVOJnCZqsJpNP1K-K{BPHDKtgVqtr91>5ufb?3bS!Pe z`T&=t<5K~FlMeSw9w<-OiK#S5ThY*IslRl#{Lp`si|>ad;tkL;{V^*^mH94qF-RT! z&<6F_NqEI?u@(hoRYt;G0@x8_Wl$`O{)q6pDjMX}^C_w2*u_YK$<7&;0TV53Z;ONy z|4v|Yhg|0ge|alqvQ5R`i5#ukd6EMa^X~Or)=#jED#Re&hiy9x{ON8DlsHY`pi+%BA^g zHCEaZ86!w+^&RR=RL=Nbchswn_5zfs-cK;!_lpTeffvU{6NqSWRk{Q?ALO<#gP&6I zrt5RTLX#Ub0Je^MYH)Uq`XyD9sku8h83!~W0qB;!*Ut%Jc^5@TpI1>9OI6s6ird9> z5UvG2*~^*wiuv0Y*gcC|XAc)ew43)|7>IQQ{=q37OeIw`$h}uOUJdY_W7E+iER1T> zH55xO01H?rAc@j9z)#7n1-Ki^suE(OoBE*EBkhu$5UPJ1OOq6xT{iA=r1A?Pe`?YH zzotY+kmZ5r`S}F!ANP%qR<*^{hHIVdzF@&)KYK<}5zRQBoDB}gnoemyy^QXA1OQAA zgor6cwXMFKeg%07jQU(En8Jr~4Tb2K47fH4?JD_Gf zd-kZ3gwLIGnB^umbEWhTg?ohpsy-x1;1u7%OrDud6CsDYi#q69GeMMPN8^ z9nyXui7mE}uG%F&{O8+=A-fIHa+_=2O8x_Lv5mEw#lPL z(!BM(5^K^z$Q=RaO%kuAtO7TX#E8KBg$lh3` zv8{or7Rqzqg&A=R)DAdq6FzMIk~6ywPV%aIu+IaFNd=<+G($ztBtC)slUtnT3M2yu zFT8e5{9rIff&8@`S~)>#HOO+s-tUoe@oX{%z%+tKjVXdN8f(Z!Tj88RrRkht7^mc+ z4~QPKBdQ5l@*6qxBBLmbRT9c|J1o5`)TX=N;#72T-|mc=<1g=H{`D@cz4l?A$NCV@ zTBaGV`KkW!O=9ONvN7^NMSfWI#TxPV{=_|NpQGOE8s<5unFTWFCZ^VN!e-48b+X8j zYlR#yC>VI+7HO4LuP~LOVf9$R~SHVGfvNW&qJ`FoCaz*7g z^gf`~h)1(1-JcrwIrOx)qbQMT>g@DFh0r^jDwU8Ts5@_`a(Vy?iTswsnpWeiJ%wq- z(u;KK$k9npsHd~O!Of3G`#I?$QJ}2%{HWIwu3P{ZLP z@P4)IF#RSw^U3#loO0)@;Vp%a*+sX*dGifR>^lwN7WzqVeUu{ThWrp1eQ?Fw@ClEp zC_84~^U9<9(J-AYni={)NUAXvE{;YAymG`&k?}8X_Bwy)*aO(<8Oj=o8tFf*Cm$Mc zA+J~{Qi~?y@#4RG6_nS(D6wCpLb-hEqFsLi1P&`a6T0!4j~`m_#XZ~p(dy85+jTUM zUsJRibAf8}Zl#@}1uZDnvjrY!iS7Q}<-VJhf8RD7YsgQNcG42yW;fKg8zkahwjah^ zI%j<%hJOCV;Uie=#_U5h2k4hOZ7ZF9x~xtk9enB|%lo#@Uqwz+E@t#oeUso0b zFgZaP7q*$F`2<(kuhiVCK(}>7-03)JW;;xwf50{s5W6?3S@+3wgAgQ@a#B5boco_&eKk+W3JXuD&mD)Y$8X_;PPZE z^x|GBO@++T=sCTCMOo4+F(`+Nk~0(t9E?~0Jgw}!kyLv3@dlviEhdYBbSzn)pY64t z!D(Q(KH<}Mx;QtAR*`Dnx;#E)N_lBWDy%#vEWRl0D{q_Ve`rQQg!^!?>#+@x*xsYmH2a>r%G?d7idP?@(|Lzzx=ec@YKF=g&( zI{jMuBqXecOp0;S(6y@LY)6%?wYEKA)jR2Z%gJBR?NaR>(yumZV-9&!$ht@sg1+X7 zdQ~idmX-9zA-lJHMJK&5O+V%P7QFyK<2_b`c}#|~O1d@MXnwA+Pb2uFK~-?3FsyBDipqlh^^G~W66V}i>;Yz(B4 zBftIE9AyZ22O#Pw-_k*=FH-KQqI<;$;^JZT>V|0NkRbG4%jqcEr2%+N?333Ygt!;y zLGZ?y%H8$%Y*{d$dd^3dNEDu*c+R1qXhFXN-~0>a3@C2bC&amE&OBzO+$F=FZ;Z{L z%-tuTRXE4N5hrCfeOxfGGn2m~LQiPnJ;Ss!#@yM9_g|L-xtB+?mA-AB|f)jC!*5G>3SQPEi+-(j-tY5Pm8I9WsfE|%$tn~*xGlCX}yjdfb zu3L+u?~=qxty%C66#-U+@b$~yeN(hiX*?6$r7n}cfAGNFFi^LdMT(7G>O!I&$AZ4} z>VdJRDD{beRrQMEA5DMAcs&AqU@&r58O_6dt!VDLxs%7)Kv5Kb^6y^rUSlBuk;H>I zy^&#IpYgR$IKgO9H)wwkvnSaKP$2BlUHn8~YR5kzJ!n|XSwUct@oryn!It50EJ%^= zuI3?)Wt$fn#W9xn481y^G#WUt4*!@aVhd>Qj`Xe{ExfgQ66fS=#GMTP7|3!cn>eT_ zNi!g7cTA*m;yxi8Mn%Ks%8D|djmaUX<@n}$S960}loPUAr{pD z@TTaa@$fE1m_c7@IoFgDkw64p6tTcjXs%sLnHyy~dlnHkj=Le3lC>2BR?R-_GG{|R zedx_5ZIMLU8Vif1Pv5&o0gd&x?kVy%dq(F5ODD4mjSLxivYQ=XQ1TIr{pT1{#d-)# z75z=4TJ!#P&%JAe*9CU<#Ldh*gvD@bnk)92D7#Rv5!8PH(rR-WF0Y$e@n8SG71zX)QJvLA{ z`WS-CM}#Kg&fW0eHqU9OK|oZVMX&XmJfTL4dr)-4&GR$$%FO0MB0?5u5`N35@ONl< zdy0e8L~Ua_$7TuV0r8$N_ORbT(*&9y4NQ`_edS26JGK7@{U*P}M}EYI{Y z5g2XC50oJ@zC$_n*6iagvI-x|cpW^D!eLn2sVB$ehc3OUE(oCIMs(Nr+%j?4j|7|D diff --git a/backend/cron/__init__.py b/backend/cron/__init__.py index 2609fec..6baa80e 100644 --- a/backend/cron/__init__.py +++ b/backend/cron/__init__.py @@ -1,7 +1,76 @@ import logging +from multiprocessing.pool import ThreadPool +from threading import Lock +from typing import Union -cron_log_handler = logging.FileHandler(CRON_LOG_FILE) +from backend import app, LrcException +from backend.models import Recorder +from backend.tools.simple_state_checker import check_capture_agent_state, ping_capture_agent + +cron_log_handler = logging.FileHandler(app.config.get('CRON_LOG_FILE')) cron_logger = logging.getLogger("mal.cron") cron_logger.addHandler(cron_log_handler) logging.getLogger("apscheduler.scheduler").addHandler(cron_log_handler) -logging.getLogger("apscheduler.executors.default").addHandler(cron_log_handler) \ No newline at end of file +logging.getLogger("apscheduler.executors.default").addHandler(cron_log_handler) + +recorder_jobs_lock = Lock() +recorder_jobs = set() + +NUM_THREADS = 8 + + +def add_recorder_to_state_check(recorder: Union[int, Recorder]): + if isinstance(recorder, int): + recorder = Recorder.get_by_identifier(recorder) + if recorder is None: + cron_logger.warning( + "Could not add recorder to state check, as specified id could not be found / recorder is None") + raise LrcException("Recorder is None / could not be found!") + recorder_jobs_lock.acquire() + recorder_jobs.add(recorder) + recorder_jobs_lock.release() + + +def remove_recorder_from_state_check(recorder: Union[int, Recorder]): + if isinstance(recorder, int): + recorder = Recorder.get_by_identifier(recorder) + if recorder is None: + cron_logger.warning( + "Could not remove recorder from state check, as specified id could not be found / recorder is None") + raise LrcException("Recorder is None / could not be found (and therefor not removed)!") + recorder_jobs_lock.acquire() + recorder_jobs.remove(recorder) + recorder_jobs_lock.release() + + +def check_recorder_state(): + recorder_jobs_lock.acquire() + recorders = list(recorder_jobs) + recorder_jobs_lock.release() + + recorder_states = {r['name']: {'state_ok': False, 'msg': 'unknown state!'} for r in recorders} + + with ThreadPool(NUM_THREADS) as pool: + results = [pool.apply_async(check_capture_agent_state, (recorder,)) for recorder in recorders] + try: + state_results = [res.get(timeout=12) for res in results] + except TimeoutError as e: + cron_logger.error("Timeout while getting capture agent state! {}".format(e)) + + for r in state_results: + if r[0]: # ok :) + recorder_states[r[2]] = {'state_ok': True} + else: + recorder_states[r[2]]['msg'] = r[1] + + with ThreadPool(NUM_THREADS) as pool: + results = [pool.apply_async(ping_capture_agent, (recorder,)) for recorder in recorders] + try: + ping_results = [res.get(timeout=12) for res in results] + except TimeoutError as e: + cron_logger.error("Timeout while pinging capture agent! {}".format(e)) + + for r in ping_results: + if not r[0]: # ok :) + recorder_states[r[2]]['msg'] = r[1] + diff --git a/backend/models/user_model.py b/backend/models/user_model.py index 0027c9b..f6b0dc7 100644 --- a/backend/models/user_model.py +++ b/backend/models/user_model.py @@ -486,6 +486,22 @@ class Permission(db.Model): back_populates='permissions') access_control_entry = db.relationship('AccessControlEntry', back_populates='required_permission') + @staticmethod + def get_by_name(name): + """ + Find permission by name + :param name: + :return: + """ + return Permission.query.filter(Permission.name == name).first() + + @staticmethod + def get_all(): + """ + Return all permissions + :return: + """ + return Permission.query.all() @event.listens_for(User.__table__, 'after_create') def insert_initial_users(*args, **kwargs): diff --git a/backend/tools/simple_state_checker.py b/backend/tools/simple_state_checker.py index 3dd9e00..c126d99 100644 --- a/backend/tools/simple_state_checker.py +++ b/backend/tools/simple_state_checker.py @@ -106,6 +106,7 @@ def get_recorder_adapter(recorder_info: dict) -> RecorderAdapter: def check_capture_agent_state(a: dict): + agent_state_error_msg = None logger.debug("Checking Agent {}".format(a['name'])) c = get_calender(a['name']) is_recording_in_calendar = len(list(c.timeline.now())) >= 1 @@ -122,6 +123,7 @@ def check_capture_agent_state(a: dict): else: logger.info(rec.get_recording_status()) logger.error("FATAL - recorder {} must be recording but is not!!!!".format(a['name'])) + agent_state_error_msg = "FATAL - recorder must be recording but is not!" with agent_states_lock: agent_states[a['name']] = 'FATAL - recorder is NOT recording, but should!' except LrcException as e: @@ -129,12 +131,14 @@ def check_capture_agent_state(a: dict): logger.error("Could not check state of recorder {}, Address: {}".format(a['name'], recorder_info['ip'])) else: logger.error("FATAL: {} is not in capturing state...but should be!!".format(a['name'])) + agent_state_error_msg = "FATAL - is not in capturing state...but should be!" else: recorder_info = get_recorder_by_name(a['name']) try: rec = get_recorder_adapter(recorder_info) if rec.is_recording(): logger.error("FATAL - recorder must not be recording!!!!") + agent_state_error_msg = "FATAL - is not in capturing state...but should be!" with agent_states_lock: agent_states[a['name']] = 'FATAL - recorder IS recording, but should NOT!' else: @@ -144,6 +148,11 @@ def check_capture_agent_state(a: dict): except LrcException as e: logger.fatal("Exception occurred: {}".format(str(e))) logger.error("Could not check state of recorder {}, Address: {}".format(a['name'], recorder_info['ip'])) + agent_state_error_msg = "FATAL - Could not check state of recorder! Address: {}".format(recorder_info['ip']) + + if agent_state_error_msg is None: + return True, "", a['name'] + return False, agent_state_error_msg, a['name'] def ping_capture_agent(a: dict): @@ -157,8 +166,10 @@ def ping_capture_agent(a: dict): universal_newlines=True # return string not bytes ) logger.info("Successfully pinged {} ({}). :-)".format(a['name'], recorder_ip)) + return True, "", a['name'] except subprocess.CalledProcessError: logger.error("Can not ping {} ({})!!".format(a['name'], recorder_ip)) + return False, "Unable to ping", a['name'] agents = get_capture_agents() diff --git a/backend/websocket/base.py b/backend/websocket/base.py index 53cdd5e..51d2671 100644 --- a/backend/websocket/base.py +++ b/backend/websocket/base.py @@ -1,6 +1,7 @@ import logging import threading +from flask_jwt_extended import verify_jwt_in_request, get_current_user, jwt_required, get_jwt_claims, get_jwt_identity from flask_login import current_user from flask_socketio import SocketIO, emit @@ -35,17 +36,16 @@ class WebSocketBase: @socketio.on('connect') def connect_handler(): logger.debug("new connection...") - print(current_user) - if current_user.is_authenticated: - logger.debug("user is authenticated") - print("allowed!") - emit('my response', - {'message': '{0} has joined'.format(current_user.name)}, - broadcast=True) - else: + try: + print(verify_jwt_in_request()) + print(get_jwt_identity()) + except: logger.info("user is not authenticated!") print("not allowed!!") return False # not allowed here + logger.debug("user is authenticated") + print("allowed!") + return True @socketio.on_error() def handle_error(self, error): @@ -55,5 +55,5 @@ class WebSocketBase: if __name__ == '__main__': wsb = WebSocketBase() - #wsb.start_websocket_in_thread(debug=True) + # wsb.start_websocket_in_thread(debug=True) wsb.start_websocket(debug=True) diff --git a/backend/websocket/handlers.py b/backend/websocket/handlers.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/websocket/websocket_base.py b/backend/websocket/websocket_base_testing.py similarity index 100% rename from backend/websocket/websocket_base.py rename to backend/websocket/websocket_base_testing.py diff --git a/backend/websocket/websocket_test_client.py b/backend/websocket/websocket_test_client.py index ea62a9c..ece025b 100644 --- a/backend/websocket/websocket_test_client.py +++ b/backend/websocket/websocket_test_client.py @@ -3,19 +3,23 @@ import logging import threading import time - -from flask import Flask -from flask_socketio import SocketIO, emit - -from backend import app - -app = Flask(__name__) -app.config['SECRET_KEY'] = 'secret!' +from socketIO_client import SocketIO, LoggingNamespace logging.basicConfig() + +token = "# replace with: JWT Access Token" +#print(token) + +print("params") +#socketIO = SocketIO('127.0.0.1', 5443, params={'jwt': '{}'.format(token)}) +print("headers") +#socketIO = SocketIO('127.0.0.1', 5443, headers={'Authorization': 'Bearer {}'.format(token)}) +print("cookies") +socketIO = SocketIO('127.0.0.1', 5443, cookies={'access_token_cookie': '{}'.format(token)}) + #socketio = SocketIO(message_queue="redis://") -socketio = SocketIO(app, port=5443, debug=True) +socketio = SocketIO('127.0.0.1', 5443) #socketio.run(app, host="localhost", port=5000) #socketio.init_app(app, host="localhost", port=5000, cors_allowed_origins="*", ) @@ -24,4 +28,4 @@ socketio = SocketIO(app, port=5443, debug=True) socketio.emit("server_event", {'data': 42, 'msg': 'toll'}) print("sent message!") socketio.emit({'data': 42, 'msg': 'toll'}) -print("sent message 2!") \ No newline at end of file +print("sent message 2!")