manager.py 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183
  1. import datetime
  2. import logging
  3. import re
  4. from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
  5. from flask import Flask, g, session, url_for
  6. from flask_appbuilder.exceptions import InvalidLoginAttempt, OAuthProviderUnknown
  7. from flask_babel import lazy_gettext as _
  8. from flask_jwt_extended import current_user as current_user_jwt
  9. from flask_jwt_extended import JWTManager
  10. from flask_limiter import Limiter
  11. from flask_limiter.util import get_remote_address
  12. from flask_login import current_user, LoginManager
  13. import jwt
  14. from werkzeug.security import check_password_hash, generate_password_hash
  15. from .api import SecurityApi
  16. from .registerviews import (
  17. RegisterUserDBView,
  18. RegisterUserOAuthView,
  19. RegisterUserOIDView,
  20. )
  21. from .views import (
  22. AuthDBView,
  23. AuthLDAPView,
  24. AuthOAuthView,
  25. AuthOIDView,
  26. AuthRemoteUserView,
  27. PermissionModelView,
  28. PermissionViewModelView,
  29. RegisterUserModelView,
  30. ResetMyPasswordView,
  31. ResetPasswordView,
  32. RoleModelView,
  33. UserDBModelView,
  34. UserInfoEditView,
  35. UserLDAPModelView,
  36. UserOAuthModelView,
  37. UserOIDModelView,
  38. UserRemoteUserModelView,
  39. UserStatsChartView,
  40. ViewMenuModelView,
  41. )
  42. from ..basemanager import BaseManager
  43. from ..const import (
  44. AUTH_DB,
  45. AUTH_LDAP,
  46. AUTH_OAUTH,
  47. AUTH_OID,
  48. AUTH_REMOTE_USER,
  49. LOGMSG_ERR_SEC_ADD_REGISTER_USER,
  50. LOGMSG_ERR_SEC_AUTH_LDAP,
  51. LOGMSG_ERR_SEC_AUTH_LDAP_TLS,
  52. LOGMSG_WAR_SEC_LOGIN_FAILED,
  53. LOGMSG_WAR_SEC_NO_USER,
  54. LOGMSG_WAR_SEC_NOLDAP_OBJ,
  55. MICROSOFT_KEY_SET_URL,
  56. PERMISSION_PREFIX,
  57. )
  58. log = logging.getLogger(__name__)
  59. class AbstractSecurityManager(BaseManager):
  60. """
  61. Abstract SecurityManager class, declares all methods used by the
  62. framework. There is no assumptions about security models or auth types.
  63. """
  64. def add_permissions_view(self, base_permissions, view_menu):
  65. """
  66. Adds a permission on a view menu to the backend
  67. :param base_permissions:
  68. list of permissions from view (all exposed methods):
  69. 'can_add','can_edit' etc...
  70. :param view_menu:
  71. name of the view or menu to add
  72. """
  73. raise NotImplementedError
  74. def add_permissions_menu(self, view_menu_name):
  75. """
  76. Adds menu_access to menu on permission_view_menu
  77. :param view_menu_name:
  78. The menu name
  79. """
  80. raise NotImplementedError
  81. def register_views(self):
  82. """
  83. Generic function to create the security views
  84. """
  85. raise NotImplementedError
  86. def is_item_public(self, permission_name, view_name):
  87. """
  88. Check if view has public permissions
  89. :param permission_name:
  90. the permission: can_show, can_edit...
  91. :param view_name:
  92. the name of the class view (child of BaseView)
  93. """
  94. raise NotImplementedError
  95. def has_access(self, permission_name, view_name):
  96. """
  97. Check if current user or public has access to view or menu
  98. """
  99. raise NotImplementedError
  100. def security_cleanup(self, baseviews, menus):
  101. raise NotImplementedError
  102. def get_first_user(self):
  103. raise NotImplementedError
  104. def noop_user_update(self, user) -> None:
  105. raise NotImplementedError
  106. def _oauth_tokengetter(token=None):
  107. """
  108. Default function to return the current user oauth token
  109. from session cookie.
  110. """
  111. token = session.get("oauth")
  112. log.debug("Token Get: %s", token)
  113. return token
  114. class BaseSecurityManager(AbstractSecurityManager):
  115. auth_view = None
  116. """ The obj instance for authentication view """
  117. user_view = None
  118. """ The obj instance for user view """
  119. registeruser_view = None
  120. """ The obj instance for registering user view """
  121. lm = None
  122. """ Flask-Login LoginManager """
  123. jwt_manager = None
  124. """ Flask-JWT-Extended """
  125. oid = None
  126. """ Flask-OpenID OpenID """
  127. oauth = None
  128. """ Flask-OAuth """
  129. oauth_remotes = None
  130. """ OAuth email whitelists """
  131. oauth_whitelists = {}
  132. """ Initialized (remote_app) providers dict {'provider_name', OBJ } """
  133. oauth_tokengetter = _oauth_tokengetter
  134. """ OAuth tokengetter function override to implement your own tokengetter method """
  135. oauth_user_info = None
  136. user_model = None
  137. """ Override to set your own User Model """
  138. role_model = None
  139. """ Override to set your own Role Model """
  140. permission_model = None
  141. """ Override to set your own Permission Model """
  142. viewmenu_model = None
  143. """ Override to set your own ViewMenu Model """
  144. permissionview_model = None
  145. """ Override to set your own PermissionView Model """
  146. registeruser_model = None
  147. """ Override to set your own RegisterUser Model """
  148. userdbmodelview = UserDBModelView
  149. """ Override if you want your own user db view """
  150. userldapmodelview = UserLDAPModelView
  151. """ Override if you want your own user ldap view """
  152. useroidmodelview = UserOIDModelView
  153. """ Override if you want your own user OID view """
  154. useroauthmodelview = UserOAuthModelView
  155. """ Override if you want your own user OAuth view """
  156. userremoteusermodelview = UserRemoteUserModelView
  157. """ Override if you want your own user REMOTE_USER view """
  158. registerusermodelview = RegisterUserModelView
  159. authdbview = AuthDBView
  160. """ Override if you want your own Authentication DB view """
  161. authldapview = AuthLDAPView
  162. """ Override if you want your own Authentication LDAP view """
  163. authoidview = AuthOIDView
  164. """ Override if you want your own Authentication OID view """
  165. authoauthview = AuthOAuthView
  166. """ Override if you want your own Authentication OAuth view """
  167. authremoteuserview = AuthRemoteUserView
  168. """ Override if you want your own Authentication REMOTE_USER view """
  169. registeruserdbview = RegisterUserDBView
  170. """ Override if you want your own register user db view """
  171. registeruseroidview = RegisterUserOIDView
  172. """ Override if you want your own register user OpenID view """
  173. registeruseroauthview = RegisterUserOAuthView
  174. """ Override if you want your own register user OAuth view """
  175. resetmypasswordview = ResetMyPasswordView
  176. """ Override if you want your own reset my password view """
  177. resetpasswordview = ResetPasswordView
  178. """ Override if you want your own reset password view """
  179. userinfoeditview = UserInfoEditView
  180. """ Override if you want your own User information edit view """
  181. # API
  182. security_api = SecurityApi
  183. """ Override if you want your own Security API login endpoint """
  184. rolemodelview = RoleModelView
  185. permissionmodelview = PermissionModelView
  186. userstatschartview = UserStatsChartView
  187. viewmenumodelview = ViewMenuModelView
  188. permissionviewmodelview = PermissionViewModelView
  189. def __init__(self, appbuilder):
  190. super(BaseSecurityManager, self).__init__(appbuilder)
  191. app = self.appbuilder.get_app
  192. # Base Security Config
  193. app.config.setdefault("AUTH_ROLE_ADMIN", "Admin")
  194. app.config.setdefault("AUTH_ROLE_PUBLIC", "Public")
  195. app.config.setdefault("AUTH_TYPE", AUTH_DB)
  196. # Self Registration
  197. app.config.setdefault("AUTH_USER_REGISTRATION", False)
  198. app.config.setdefault("AUTH_USER_REGISTRATION_ROLE", self.auth_role_public)
  199. app.config.setdefault("AUTH_USER_REGISTRATION_ROLE_JMESPATH", None)
  200. # Role Mapping
  201. app.config.setdefault("AUTH_ROLES_MAPPING", {})
  202. app.config.setdefault("AUTH_ROLES_SYNC_AT_LOGIN", False)
  203. app.config.setdefault("AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS", False)
  204. app.config.setdefault(
  205. "AUTH_DB_FAKE_PASSWORD_HASH_CHECK",
  206. "scrypt:32768:8:1$wiDa0ruWlIPhp9LM$6e409d093e62ad54df2af895d0e125b05ff6cf6414"
  207. "8350189ffc4bcc71286edf1b8ad94a442c00f890224bf2b32153d0750c89ee9"
  208. "401e62f9dcee5399065e4e5",
  209. )
  210. # LDAP Config
  211. if self.auth_type == AUTH_LDAP:
  212. if "AUTH_LDAP_SERVER" not in app.config:
  213. raise Exception(
  214. "No AUTH_LDAP_SERVER defined on config"
  215. " with AUTH_LDAP authentication type."
  216. )
  217. app.config.setdefault("AUTH_LDAP_SEARCH", "")
  218. app.config.setdefault("AUTH_LDAP_SEARCH_FILTER", "")
  219. app.config.setdefault("AUTH_LDAP_APPEND_DOMAIN", "")
  220. app.config.setdefault("AUTH_LDAP_USERNAME_FORMAT", "")
  221. app.config.setdefault("AUTH_LDAP_BIND_USER", "")
  222. app.config.setdefault("AUTH_LDAP_BIND_PASSWORD", "")
  223. # TLS options
  224. app.config.setdefault("AUTH_LDAP_USE_TLS", False)
  225. app.config.setdefault("AUTH_LDAP_ALLOW_SELF_SIGNED", False)
  226. app.config.setdefault("AUTH_LDAP_TLS_DEMAND", False)
  227. app.config.setdefault("AUTH_LDAP_TLS_CACERTDIR", "")
  228. app.config.setdefault("AUTH_LDAP_TLS_CACERTFILE", "")
  229. app.config.setdefault("AUTH_LDAP_TLS_CERTFILE", "")
  230. app.config.setdefault("AUTH_LDAP_TLS_KEYFILE", "")
  231. # Mapping options
  232. app.config.setdefault("AUTH_LDAP_UID_FIELD", "uid")
  233. app.config.setdefault("AUTH_LDAP_GROUP_FIELD", "memberOf")
  234. app.config.setdefault("AUTH_LDAP_FIRSTNAME_FIELD", "givenName")
  235. app.config.setdefault("AUTH_LDAP_LASTNAME_FIELD", "sn")
  236. app.config.setdefault("AUTH_LDAP_EMAIL_FIELD", "mail")
  237. if self.auth_type == AUTH_REMOTE_USER:
  238. app.config.setdefault("AUTH_REMOTE_USER_ENV_VAR", "REMOTE_USER")
  239. # Rate limiting
  240. app.config.setdefault("AUTH_RATE_LIMITED", False)
  241. app.config.setdefault("AUTH_RATE_LIMIT", "10 per 20 second")
  242. if self.auth_type == AUTH_OID:
  243. from flask_openid import OpenID
  244. log.warning(
  245. "AUTH_OID is deprecated and will be removed in version 5. "
  246. "Migrate to other authentication methods."
  247. )
  248. self.oid = OpenID(app)
  249. if self.auth_type == AUTH_OAUTH:
  250. from authlib.integrations.flask_client import OAuth
  251. self.oauth = OAuth(app)
  252. self.oauth_remotes = {}
  253. for _provider in self.oauth_providers:
  254. provider_name = _provider["name"]
  255. log.debug("OAuth providers init %s", provider_name)
  256. obj_provider = self.oauth.register(
  257. provider_name, **_provider["remote_app"]
  258. )
  259. obj_provider._tokengetter = self.oauth_tokengetter
  260. if not self.oauth_user_info:
  261. self.oauth_user_info = self.get_oauth_user_info
  262. # Whitelist only users with matching emails
  263. if "whitelist" in _provider:
  264. self.oauth_whitelists[provider_name] = _provider["whitelist"]
  265. self.oauth_remotes[provider_name] = obj_provider
  266. self._builtin_roles = self.create_builtin_roles()
  267. # Setup Flask-Login
  268. self.lm = self.create_login_manager(app)
  269. # Setup Flask-Jwt-Extended
  270. self.jwt_manager = self.create_jwt_manager(app)
  271. # Setup Flask-Limiter
  272. self.limiter = self.create_limiter(app)
  273. def create_limiter(self, app: Flask) -> Limiter:
  274. limiter = Limiter(
  275. key_func=app.config.get("RATELIMIT_KEY_FUNC", get_remote_address)
  276. )
  277. limiter.init_app(app)
  278. return limiter
  279. def create_login_manager(self, app) -> LoginManager:
  280. """
  281. Override to implement your custom login manager instance
  282. :param app: Flask app
  283. """
  284. lm = LoginManager(app)
  285. lm.login_view = "login"
  286. lm.user_loader(self.load_user)
  287. return lm
  288. def create_jwt_manager(self, app) -> JWTManager:
  289. """
  290. Override to implement your custom JWT manager instance
  291. :param app: Flask app
  292. """
  293. jwt_manager = JWTManager()
  294. jwt_manager.init_app(app)
  295. jwt_manager.user_lookup_loader(self.load_user_jwt)
  296. return jwt_manager
  297. def create_builtin_roles(self):
  298. return self.appbuilder.get_app.config.get("FAB_ROLES", {})
  299. def get_roles_from_keys(self, role_keys: List[str]) -> Set[role_model]:
  300. """
  301. Construct a list of FAB role objects, from a list of keys.
  302. NOTE:
  303. - keys are things like: "LDAP group DNs" or "OAUTH group names"
  304. - we use AUTH_ROLES_MAPPING to map from keys, to FAB role names
  305. :param role_keys: the list of FAB role keys
  306. :return: a list of RoleModelView
  307. """
  308. _roles = set()
  309. _role_keys = set(role_keys)
  310. for role_key, fab_role_names in self.auth_roles_mapping.items():
  311. if role_key in _role_keys:
  312. for fab_role_name in fab_role_names:
  313. fab_role = self.find_role(fab_role_name)
  314. if fab_role:
  315. _roles.add(fab_role)
  316. else:
  317. log.warning(
  318. "Can't find role specified in AUTH_ROLES_MAPPING: %s",
  319. fab_role_name,
  320. )
  321. return _roles
  322. @property
  323. def auth_type_provider_name(self) -> Optional[str]:
  324. provider_to_auth_type = {AUTH_DB: "db", AUTH_LDAP: "ldap"}
  325. return provider_to_auth_type.get(self.auth_type)
  326. @property
  327. def get_url_for_registeruser(self):
  328. return url_for(
  329. "%s.%s"
  330. % (self.registeruser_view.endpoint, self.registeruser_view.default_view)
  331. )
  332. @property
  333. def get_user_datamodel(self):
  334. return self.user_view.datamodel
  335. @property
  336. def get_register_user_datamodel(self):
  337. return self.registerusermodelview.datamodel
  338. @property
  339. def builtin_roles(self) -> Dict[str, Any]:
  340. return self._builtin_roles
  341. @property
  342. def api_login_allow_multiple_providers(self):
  343. return self.appbuilder.get_app.config["AUTH_API_LOGIN_ALLOW_MULTIPLE_PROVIDERS"]
  344. @property
  345. def auth_type(self) -> int:
  346. return self.appbuilder.get_app.config["AUTH_TYPE"]
  347. @property
  348. def auth_username_ci(self) -> str:
  349. return self.appbuilder.get_app.config.get("AUTH_USERNAME_CI", True)
  350. @property
  351. def auth_role_admin(self) -> str:
  352. return self.appbuilder.get_app.config["AUTH_ROLE_ADMIN"]
  353. @property
  354. def auth_role_public(self) -> str:
  355. return self.appbuilder.get_app.config["AUTH_ROLE_PUBLIC"]
  356. @property
  357. def auth_ldap_server(self) -> str:
  358. return self.appbuilder.get_app.config["AUTH_LDAP_SERVER"]
  359. @property
  360. def auth_ldap_use_tls(self) -> bool:
  361. return self.appbuilder.get_app.config["AUTH_LDAP_USE_TLS"]
  362. @property
  363. def auth_user_registration(self) -> bool:
  364. return self.appbuilder.get_app.config["AUTH_USER_REGISTRATION"]
  365. @property
  366. def auth_user_registration_role(self) -> str:
  367. return self.appbuilder.get_app.config["AUTH_USER_REGISTRATION_ROLE"]
  368. @property
  369. def auth_user_registration_role_jmespath(self) -> str:
  370. return self.appbuilder.get_app.config["AUTH_USER_REGISTRATION_ROLE_JMESPATH"]
  371. @property
  372. def auth_remote_user_env_var(self) -> str:
  373. return self.appbuilder.get_app.config["AUTH_REMOTE_USER_ENV_VAR"]
  374. @property
  375. def auth_roles_mapping(self) -> Dict[str, List[str]]:
  376. return self.appbuilder.get_app.config["AUTH_ROLES_MAPPING"]
  377. @property
  378. def auth_roles_sync_at_login(self) -> bool:
  379. return self.appbuilder.get_app.config["AUTH_ROLES_SYNC_AT_LOGIN"]
  380. @property
  381. def auth_ldap_search(self):
  382. return self.appbuilder.get_app.config["AUTH_LDAP_SEARCH"]
  383. @property
  384. def auth_ldap_search_filter(self):
  385. return self.appbuilder.get_app.config["AUTH_LDAP_SEARCH_FILTER"]
  386. @property
  387. def auth_ldap_bind_user(self):
  388. return self.appbuilder.get_app.config["AUTH_LDAP_BIND_USER"]
  389. @property
  390. def auth_ldap_bind_password(self):
  391. return self.appbuilder.get_app.config["AUTH_LDAP_BIND_PASSWORD"]
  392. @property
  393. def auth_ldap_append_domain(self):
  394. return self.appbuilder.get_app.config["AUTH_LDAP_APPEND_DOMAIN"]
  395. @property
  396. def auth_ldap_username_format(self):
  397. return self.appbuilder.get_app.config["AUTH_LDAP_USERNAME_FORMAT"]
  398. @property
  399. def auth_ldap_uid_field(self):
  400. return self.appbuilder.get_app.config["AUTH_LDAP_UID_FIELD"]
  401. @property
  402. def auth_ldap_group_field(self) -> str:
  403. return self.appbuilder.get_app.config["AUTH_LDAP_GROUP_FIELD"]
  404. @property
  405. def auth_ldap_firstname_field(self):
  406. return self.appbuilder.get_app.config["AUTH_LDAP_FIRSTNAME_FIELD"]
  407. @property
  408. def auth_ldap_lastname_field(self):
  409. return self.appbuilder.get_app.config["AUTH_LDAP_LASTNAME_FIELD"]
  410. @property
  411. def auth_ldap_email_field(self):
  412. return self.appbuilder.get_app.config["AUTH_LDAP_EMAIL_FIELD"]
  413. @property
  414. def auth_ldap_bind_first(self):
  415. return self.appbuilder.get_app.config["AUTH_LDAP_BIND_FIRST"]
  416. @property
  417. def auth_ldap_allow_self_signed(self):
  418. return self.appbuilder.get_app.config["AUTH_LDAP_ALLOW_SELF_SIGNED"]
  419. @property
  420. def auth_ldap_tls_demand(self):
  421. return self.appbuilder.get_app.config["AUTH_LDAP_TLS_DEMAND"]
  422. @property
  423. def auth_ldap_tls_cacertdir(self):
  424. return self.appbuilder.get_app.config["AUTH_LDAP_TLS_CACERTDIR"]
  425. @property
  426. def auth_ldap_tls_cacertfile(self):
  427. return self.appbuilder.get_app.config["AUTH_LDAP_TLS_CACERTFILE"]
  428. @property
  429. def auth_ldap_tls_certfile(self):
  430. return self.appbuilder.get_app.config["AUTH_LDAP_TLS_CERTFILE"]
  431. @property
  432. def auth_ldap_tls_keyfile(self):
  433. return self.appbuilder.get_app.config["AUTH_LDAP_TLS_KEYFILE"]
  434. @property
  435. def openid_providers(self):
  436. return self.appbuilder.get_app.config["OPENID_PROVIDERS"]
  437. @property
  438. def oauth_providers(self):
  439. return self.appbuilder.get_app.config["OAUTH_PROVIDERS"]
  440. @property
  441. def is_auth_limited(self) -> bool:
  442. return self.appbuilder.get_app.config["AUTH_RATE_LIMITED"]
  443. @property
  444. def auth_rate_limit(self) -> str:
  445. return self.appbuilder.get_app.config["AUTH_RATE_LIMIT"]
  446. @property
  447. def current_user(self):
  448. if current_user.is_authenticated:
  449. return g.user
  450. elif current_user_jwt:
  451. return current_user_jwt
  452. def oauth_user_info_getter(
  453. self,
  454. func: Callable[["BaseSecurityManager", str, Dict[str, Any]], Dict[str, Any]],
  455. ):
  456. """
  457. Decorator function to be the OAuth user info getter
  458. for all the providers, receives provider and response
  459. return a dict with the information returned from the provider.
  460. The returned user info dict should have it's keys with the same
  461. name as the User Model.
  462. Use it like this an example for GitHub ::
  463. @appbuilder.sm.oauth_user_info_getter
  464. def my_oauth_user_info(sm, provider, response=None):
  465. if provider == 'github':
  466. me = sm.oauth_remotes[provider].get('user')
  467. return {'username': me.data.get('login')}
  468. return {}
  469. """
  470. def wraps(provider: str, response: Dict[str, Any] = None) -> Dict[str, Any]:
  471. return func(self, provider, response)
  472. self.oauth_user_info = wraps
  473. return wraps
  474. def get_oauth_token_key_name(self, provider):
  475. """
  476. Returns the token_key name for the oauth provider
  477. if none is configured defaults to oauth_token
  478. this is configured using OAUTH_PROVIDERS and token_key key.
  479. """
  480. for _provider in self.oauth_providers:
  481. if _provider["name"] == provider:
  482. return _provider.get("token_key", "oauth_token")
  483. def get_oauth_token_secret_name(self, provider):
  484. """
  485. Returns the token_secret name for the oauth provider
  486. if none is configured defaults to oauth_secret
  487. this is configured using OAUTH_PROVIDERS and token_secret
  488. """
  489. for _provider in self.oauth_providers:
  490. if _provider["name"] == provider:
  491. return _provider.get("token_secret", "oauth_token_secret")
  492. def set_oauth_session(self, provider, oauth_response):
  493. """
  494. Set the current session with OAuth user secrets
  495. """
  496. # Get this provider key names for token_key and token_secret
  497. token_key = self.appbuilder.sm.get_oauth_token_key_name(provider)
  498. token_secret = self.appbuilder.sm.get_oauth_token_secret_name(provider)
  499. # Save users token on encrypted session cookie
  500. session["oauth"] = (
  501. oauth_response[token_key],
  502. oauth_response.get(token_secret, ""),
  503. )
  504. session["oauth_provider"] = provider
  505. def get_oauth_user_info(
  506. self, provider: str, resp: Dict[str, Any]
  507. ) -> Dict[str, Any]:
  508. """
  509. Since there are different OAuth APIs with different ways to
  510. retrieve user info
  511. """
  512. # for GITHUB
  513. if provider == "github" or provider == "githublocal":
  514. me = self.appbuilder.sm.oauth_remotes[provider].get("user")
  515. data = me.json()
  516. log.debug("User info from Github: %s", data)
  517. return {"username": "github_" + data.get("login")}
  518. # for twitter
  519. if provider == "twitter":
  520. me = self.appbuilder.sm.oauth_remotes[provider].get("account/settings.json")
  521. data = me.json()
  522. log.debug("User info from Twitter: %s", data)
  523. return {"username": "twitter_" + data.get("screen_name", "")}
  524. # for linkedin
  525. if provider == "linkedin":
  526. me = self.appbuilder.sm.oauth_remotes[provider].get(
  527. "people/~:(id,email-address,first-name,last-name)?format=json"
  528. )
  529. data = me.json()
  530. log.debug("User info from Linkedin: %s", data)
  531. return {
  532. "username": "linkedin_" + data.get("id", ""),
  533. "email": data.get("email-address", ""),
  534. "first_name": data.get("firstName", ""),
  535. "last_name": data.get("lastName", ""),
  536. }
  537. # for Google
  538. if provider == "google":
  539. me = self.appbuilder.sm.oauth_remotes[provider].get("userinfo")
  540. data = me.json()
  541. log.debug("User info from Google: %s", data)
  542. return {
  543. "username": "google_" + data.get("id", ""),
  544. "first_name": data.get("given_name", ""),
  545. "last_name": data.get("family_name", ""),
  546. "email": data.get("email", ""),
  547. }
  548. if provider == "azure":
  549. me = self._decode_and_validate_azure_jwt(resp["id_token"])
  550. log.debug("User info from Azure: %s", me)
  551. # https://learn.microsoft.com/en-us/azure/active-directory/develop/id-token-claims-reference#payload-claims
  552. return {
  553. # To keep backward compatibility with previous versions
  554. # of FAB, we use upn if available, otherwise we use email
  555. "email": me["upn"] if "upn" in me else me["email"],
  556. "first_name": me.get("given_name", ""),
  557. "last_name": me.get("family_name", ""),
  558. "username": me["oid"],
  559. "role_keys": me.get("roles", []),
  560. }
  561. # for OpenShift
  562. if provider == "openshift":
  563. me = self.appbuilder.sm.oauth_remotes[provider].get(
  564. "apis/user.openshift.io/v1/users/~"
  565. )
  566. data = me.json()
  567. log.debug("User info from OpenShift: %s", data)
  568. return {"username": "openshift_" + data.get("metadata").get("name")}
  569. # for Okta
  570. if provider == "okta":
  571. me = self.appbuilder.sm.oauth_remotes[provider].get("userinfo")
  572. data = me.json()
  573. log.debug("User info from Okta: %s", data)
  574. if "error" not in data:
  575. return {
  576. "username": f"{provider}_{data['sub']}",
  577. "first_name": data.get("given_name", ""),
  578. "last_name": data.get("family_name", ""),
  579. "email": data["email"],
  580. "role_keys": data.get("groups", []),
  581. }
  582. else:
  583. log.error(data.get("error_description"))
  584. return {}
  585. # for Auth0
  586. if provider == "auth0":
  587. data = self.appbuilder.sm.oauth_remotes[provider].userinfo()
  588. log.debug("User info from Auth0: %s", data)
  589. return {
  590. "username": f"{provider}_{data['sub']}",
  591. "first_name": data.get("given_name", ""),
  592. "last_name": data.get("family_name", ""),
  593. "email": data["email"],
  594. "role_keys": data.get("groups", []),
  595. }
  596. # for Keycloak
  597. if provider in ["keycloak", "keycloak_before_17"]:
  598. me = self.appbuilder.sm.oauth_remotes[provider].get(
  599. "openid-connect/userinfo"
  600. )
  601. me.raise_for_status()
  602. data = me.json()
  603. log.debug("User info from Keycloak: %s", data)
  604. return {
  605. "username": data.get("preferred_username", ""),
  606. "first_name": data.get("given_name", ""),
  607. "last_name": data.get("family_name", ""),
  608. "email": data.get("email", ""),
  609. "role_keys": data.get("groups", []),
  610. }
  611. # for Authentik
  612. if provider == "authentik":
  613. id_token = resp["id_token"]
  614. me = self._get_authentik_token_info(id_token)
  615. log.debug("User info from authentik: %s", me)
  616. return {
  617. "email": me["preferred_username"],
  618. "first_name": me.get("given_name", ""),
  619. "username": me["nickname"],
  620. "role_keys": me.get("groups", []),
  621. }
  622. raise OAuthProviderUnknown()
  623. def _get_microsoft_jwks(self) -> List[Dict[str, Any]]:
  624. import requests
  625. return requests.get(MICROSOFT_KEY_SET_URL).json()
  626. def _decode_and_validate_azure_jwt(self, id_token: str) -> Dict[str, str]:
  627. verify_signature = self.oauth_remotes["azure"].client_kwargs.get(
  628. "verify_signature", False
  629. )
  630. if verify_signature:
  631. from authlib.jose import JsonWebKey, jwt as authlib_jwt
  632. keyset = JsonWebKey.import_key_set(self._get_microsoft_jwks())
  633. claims = authlib_jwt.decode(id_token, keyset)
  634. claims.validate()
  635. return claims
  636. return jwt.decode(id_token, options={"verify_signature": False})
  637. def _get_authentik_jwks(self, jwks_url) -> dict:
  638. import requests
  639. resp = requests.get(jwks_url)
  640. if resp.status_code == 200:
  641. return resp.json()
  642. return False
  643. def _validate_jwt(self, id_token, jwks):
  644. from authlib.jose import JsonWebKey, jwt as authlib_jwt
  645. keyset = JsonWebKey.import_key_set(jwks)
  646. claims = authlib_jwt.decode(id_token, keyset)
  647. claims.validate()
  648. log.info("JWT token is validated")
  649. return claims
  650. def _get_authentik_token_info(self, id_token):
  651. me = jwt.decode(id_token, options={"verify_signature": False})
  652. verify_signature = self.oauth_remotes["authentik"].client_kwargs.get(
  653. "verify_signature", True
  654. )
  655. if verify_signature:
  656. # Validate the token using authentik certificate
  657. jwks_uri = self.oauth_remotes["authentik"].server_metadata.get("jwks_uri")
  658. if jwks_uri:
  659. jwks = self._get_authentik_jwks(jwks_uri)
  660. if jwks:
  661. return self._validate_jwt(id_token, jwks)
  662. else:
  663. log.error(
  664. "jwks_uri not specified in OAuth Providers, "
  665. "could not verify token signature"
  666. )
  667. else:
  668. # Return the token info without validating
  669. log.warning("JWT token is not validated!")
  670. return me
  671. raise InvalidLoginAttempt("OAuth signature verify failed")
  672. def register_views(self):
  673. if not self.appbuilder.app.config.get("FAB_ADD_SECURITY_VIEWS", True):
  674. return
  675. # Security APIs
  676. self.appbuilder.add_api(self.security_api)
  677. if self.auth_user_registration:
  678. if self.auth_type == AUTH_DB:
  679. self.registeruser_view = self.registeruserdbview()
  680. elif self.auth_type == AUTH_OID:
  681. self.registeruser_view = self.registeruseroidview()
  682. elif self.auth_type == AUTH_OAUTH:
  683. self.registeruser_view = self.registeruseroauthview()
  684. if self.registeruser_view:
  685. self.appbuilder.add_view_no_menu(self.registeruser_view)
  686. self.appbuilder.add_view_no_menu(self.resetpasswordview())
  687. self.appbuilder.add_view_no_menu(self.resetmypasswordview())
  688. self.appbuilder.add_view_no_menu(self.userinfoeditview())
  689. if self.auth_type == AUTH_DB:
  690. self.user_view = self.userdbmodelview
  691. self.auth_view = self.authdbview()
  692. elif self.auth_type == AUTH_LDAP:
  693. self.user_view = self.userldapmodelview
  694. self.auth_view = self.authldapview()
  695. elif self.auth_type == AUTH_OAUTH:
  696. self.user_view = self.useroauthmodelview
  697. self.auth_view = self.authoauthview()
  698. elif self.auth_type == AUTH_REMOTE_USER:
  699. self.user_view = self.userremoteusermodelview
  700. self.auth_view = self.authremoteuserview()
  701. else:
  702. self.user_view = self.useroidmodelview
  703. self.auth_view = self.authoidview()
  704. if self.auth_user_registration:
  705. pass
  706. # self.registeruser_view = self.registeruseroidview()
  707. # self.appbuilder.add_view_no_menu(self.registeruser_view)
  708. self.appbuilder.add_view_no_menu(self.auth_view)
  709. # this needs to be done after the view is added, otherwise the blueprint
  710. # is not initialized
  711. if self.is_auth_limited:
  712. self.limiter.limit(self.auth_rate_limit, methods=["POST"])(
  713. self.auth_view.blueprint
  714. )
  715. self.user_view = self.appbuilder.add_view(
  716. self.user_view,
  717. "List Users",
  718. icon="fa-user",
  719. label=_("List Users"),
  720. category="Security",
  721. category_icon="fa-cogs",
  722. category_label=_("Security"),
  723. )
  724. role_view = self.appbuilder.add_view(
  725. self.rolemodelview,
  726. "List Roles",
  727. icon="fa-group",
  728. label=_("List Roles"),
  729. category="Security",
  730. category_icon="fa-cogs",
  731. )
  732. role_view.related_views = [self.user_view.__class__]
  733. if self.userstatschartview:
  734. self.appbuilder.add_view(
  735. self.userstatschartview,
  736. "User's Statistics",
  737. icon="fa-bar-chart-o",
  738. label=_("User's Statistics"),
  739. category="Security",
  740. )
  741. if self.auth_user_registration:
  742. self.appbuilder.add_view(
  743. self.registerusermodelview,
  744. "User Registrations",
  745. icon="fa-user-plus",
  746. label=_("User Registrations"),
  747. category="Security",
  748. )
  749. self.appbuilder.menu.add_separator("Security")
  750. if self.appbuilder.app.config.get("FAB_ADD_SECURITY_PERMISSION_VIEW", True):
  751. self.appbuilder.add_view(
  752. self.permissionmodelview,
  753. "Base Permissions",
  754. icon="fa-lock",
  755. label=_("Base Permissions"),
  756. category="Security",
  757. )
  758. if self.appbuilder.app.config.get("FAB_ADD_SECURITY_VIEW_MENU_VIEW", True):
  759. self.appbuilder.add_view(
  760. self.viewmenumodelview,
  761. "Views/Menus",
  762. icon="fa-list-alt",
  763. label=_("Views/Menus"),
  764. category="Security",
  765. )
  766. if self.appbuilder.app.config.get(
  767. "FAB_ADD_SECURITY_PERMISSION_VIEWS_VIEW", True
  768. ):
  769. self.appbuilder.add_view(
  770. self.permissionviewmodelview,
  771. "Permission on Views/Menus",
  772. icon="fa-link",
  773. label=_("Permission on Views/Menus"),
  774. category="Security",
  775. )
  776. def create_db(self):
  777. """
  778. Setups the DB, creates admin and public roles if they don't exist.
  779. """
  780. roles_mapping = self.appbuilder.get_app.config.get("FAB_ROLES_MAPPING", {})
  781. for pk, name in roles_mapping.items():
  782. self.update_role(pk, name)
  783. for role_name, permission_view_menus in self.builtin_roles.items():
  784. permission_view_menus = [
  785. self.add_permission_view_menu(permission_name, view_menu_name)
  786. for view_menu_name, permission_name in permission_view_menus
  787. ]
  788. self.add_role(name=role_name, permissions=permission_view_menus)
  789. if self.auth_role_admin not in self.builtin_roles:
  790. self.add_role(self.auth_role_admin)
  791. self.add_role(self.auth_role_public)
  792. if self.count_users() == 0:
  793. log.warning(LOGMSG_WAR_SEC_NO_USER)
  794. def reset_password(self, userid, password):
  795. """
  796. Change/Reset a user's password for authdb.
  797. Password will be hashed and saved.
  798. :param userid:
  799. the user.id to reset the password
  800. :param password:
  801. The clear text password to reset and save hashed on the db
  802. """
  803. user = self.get_user_by_id(userid)
  804. user.password = generate_password_hash(password)
  805. self.update_user(user)
  806. def update_user_auth_stat(self, user, success=True):
  807. """
  808. Update user authentication stats upon successful/unsuccessful
  809. authentication attempts.
  810. :param user:
  811. The identified (but possibly not successfully authenticated) user
  812. model
  813. :param success:
  814. :type success: bool or None
  815. Defaults to true, if true increments login_count, updates
  816. last_login, and resets fail_login_count to 0, if false increments
  817. fail_login_count on user model.
  818. """
  819. if not user.login_count:
  820. user.login_count = 0
  821. if not user.fail_login_count:
  822. user.fail_login_count = 0
  823. if success:
  824. user.login_count += 1
  825. user.last_login = datetime.datetime.now()
  826. user.fail_login_count = 0
  827. else:
  828. user.fail_login_count += 1
  829. self.update_user(user)
  830. def auth_user_db(self, username, password):
  831. """
  832. Method for authenticating user, auth db style
  833. :param username:
  834. The username or registered email address
  835. :param password:
  836. The password, will be tested against hashed password on db
  837. """
  838. if username is None or username == "":
  839. return None
  840. first_user = self.get_first_user()
  841. user = self.find_user(username=username)
  842. if user is None:
  843. user = self.find_user(email=username)
  844. else:
  845. # Balance failure and success
  846. _ = self.find_user(email=username)
  847. if user is None or (not user.is_active):
  848. # Balance failure and success
  849. check_password_hash(
  850. self.appbuilder.get_app.config["AUTH_DB_FAKE_PASSWORD_HASH_CHECK"],
  851. "password",
  852. )
  853. log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
  854. # Balance failure and success
  855. if first_user:
  856. self.noop_user_update(first_user)
  857. return None
  858. elif check_password_hash(user.password, password):
  859. self.update_user_auth_stat(user, True)
  860. return user
  861. else:
  862. self.update_user_auth_stat(user, False)
  863. log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
  864. return None
  865. def _search_ldap(self, ldap, con, username):
  866. """
  867. Searches LDAP for user.
  868. :param ldap: The ldap module reference
  869. :param con: The ldap connection
  870. :param username: username to match with AUTH_LDAP_UID_FIELD
  871. :return: ldap object array
  872. """
  873. # always check AUTH_LDAP_SEARCH is set before calling this method
  874. assert self.auth_ldap_search, "AUTH_LDAP_SEARCH must be set"
  875. # build the filter string for the LDAP search
  876. if self.auth_ldap_search_filter:
  877. filter_str = "(&{0}({1}={2}))".format(
  878. self.auth_ldap_search_filter, self.auth_ldap_uid_field, username
  879. )
  880. else:
  881. filter_str = "({0}={1})".format(self.auth_ldap_uid_field, username)
  882. # build what fields to request in the LDAP search
  883. request_fields = [
  884. self.auth_ldap_firstname_field,
  885. self.auth_ldap_lastname_field,
  886. self.auth_ldap_email_field,
  887. ]
  888. if len(self.auth_roles_mapping) > 0:
  889. request_fields.append(self.auth_ldap_group_field)
  890. # perform the LDAP search
  891. log.debug(
  892. "LDAP search for '%s' with fields %s in scope '%s'",
  893. filter_str,
  894. request_fields,
  895. self.auth_ldap_search,
  896. )
  897. raw_search_result = con.search_s(
  898. self.auth_ldap_search, ldap.SCOPE_SUBTREE, filter_str, request_fields
  899. )
  900. log.debug("LDAP search returned: %s", raw_search_result)
  901. # Remove any search referrals from results
  902. search_result = [
  903. (dn, attrs)
  904. for dn, attrs in raw_search_result
  905. if dn is not None and isinstance(attrs, dict)
  906. ]
  907. # only continue if 0 or 1 results were returned
  908. if len(search_result) > 1:
  909. log.error(
  910. "LDAP search for '%s' in scope '%s' returned multiple results",
  911. filter_str,
  912. self.auth_ldap_search,
  913. )
  914. return None, None
  915. try:
  916. # extract the DN
  917. user_dn = search_result[0][0]
  918. # extract the other attributes
  919. user_info = search_result[0][1]
  920. # return
  921. return user_dn, user_info
  922. except (IndexError, NameError):
  923. return None, None
  924. def _ldap_calculate_user_roles(
  925. self, user_attributes: Dict[str, bytes]
  926. ) -> List[str]:
  927. user_role_objects = set()
  928. # apply AUTH_ROLES_MAPPING
  929. if len(self.auth_roles_mapping) > 0:
  930. user_role_keys = self.ldap_extract_list(
  931. user_attributes, self.auth_ldap_group_field
  932. )
  933. user_role_objects.update(self.get_roles_from_keys(user_role_keys))
  934. # apply AUTH_USER_REGISTRATION
  935. if self.auth_user_registration:
  936. registration_role_name = self.auth_user_registration_role
  937. # lookup registration role in flask db
  938. fab_role = self.find_role(registration_role_name)
  939. if fab_role:
  940. user_role_objects.add(fab_role)
  941. else:
  942. log.warning(
  943. "Can't find AUTH_USER_REGISTRATION role: %s", registration_role_name
  944. )
  945. return list(user_role_objects)
  946. def _ldap_bind_indirect(self, ldap, con) -> None:
  947. """
  948. Attempt to bind to LDAP using the AUTH_LDAP_BIND_USER.
  949. :param ldap: The ldap module reference
  950. :param con: The ldap connection
  951. """
  952. # always check AUTH_LDAP_BIND_USER is set before calling this method
  953. assert self.auth_ldap_bind_user, "AUTH_LDAP_BIND_USER must be set"
  954. try:
  955. log.debug(
  956. "LDAP bind indirect TRY with username: '%s'", self.auth_ldap_bind_user
  957. )
  958. con.simple_bind_s(self.auth_ldap_bind_user, self.auth_ldap_bind_password)
  959. log.debug(
  960. "LDAP bind indirect SUCCESS with username: '%s'",
  961. self.auth_ldap_bind_user,
  962. )
  963. except ldap.INVALID_CREDENTIALS as ex:
  964. log.error(
  965. "AUTH_LDAP_BIND_USER and AUTH_LDAP_BIND_PASSWORD are"
  966. " not valid LDAP bind credentials"
  967. )
  968. raise ex
  969. @staticmethod
  970. def _ldap_bind(ldap, con, dn: str, password: str) -> bool:
  971. """
  972. Validates/binds the provided dn/password with the LDAP sever.
  973. """
  974. try:
  975. log.debug("LDAP bind TRY with username: '%s'", dn)
  976. con.simple_bind_s(dn, password)
  977. log.debug("LDAP bind SUCCESS with username: '%s'", dn)
  978. return True
  979. except ldap.INVALID_CREDENTIALS:
  980. return False
  981. @staticmethod
  982. def ldap_extract(
  983. ldap_dict: Dict[str, bytes], field_name: str, fallback: str
  984. ) -> str:
  985. raw_value = ldap_dict.get(field_name, [bytes()])
  986. # decode - if empty string, default to fallback, otherwise take first element
  987. return raw_value[0].decode("utf-8") or fallback
  988. @staticmethod
  989. def ldap_extract_list(ldap_dict: Dict[str, bytes], field_name: str) -> List[str]:
  990. raw_list = ldap_dict.get(field_name, [])
  991. # decode - removing empty strings
  992. return [x.decode("utf-8") for x in raw_list if x.decode("utf-8")]
  993. def auth_user_ldap(self, username, password):
  994. """
  995. Method for authenticating user with LDAP.
  996. NOTE: this depends on python-ldap module
  997. :param username: the username
  998. :param password: the password
  999. """
  1000. # If no username is provided, go away
  1001. if (username is None) or username == "":
  1002. return None
  1003. # Search the DB for this user
  1004. user = self.find_user(username=username)
  1005. # If user is not active, go away
  1006. if user and (not user.is_active):
  1007. return None
  1008. # If user is not registered, and not self-registration, go away
  1009. if (not user) and (not self.auth_user_registration):
  1010. return None
  1011. # Ensure python-ldap is installed
  1012. try:
  1013. import ldap
  1014. except ImportError:
  1015. log.error("python-ldap library is not installed")
  1016. return None
  1017. try:
  1018. # LDAP certificate settings
  1019. if self.auth_ldap_tls_cacertdir:
  1020. ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.auth_ldap_tls_cacertdir)
  1021. if self.auth_ldap_tls_cacertfile:
  1022. ldap.set_option(
  1023. ldap.OPT_X_TLS_CACERTFILE, self.auth_ldap_tls_cacertfile
  1024. )
  1025. if self.auth_ldap_tls_certfile:
  1026. ldap.set_option(ldap.OPT_X_TLS_CERTFILE, self.auth_ldap_tls_certfile)
  1027. if self.auth_ldap_tls_keyfile:
  1028. ldap.set_option(ldap.OPT_X_TLS_KEYFILE, self.auth_ldap_tls_keyfile)
  1029. if self.auth_ldap_allow_self_signed:
  1030. ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
  1031. ldap.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
  1032. elif self.auth_ldap_tls_demand:
  1033. ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
  1034. ldap.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
  1035. # Initialise LDAP connection
  1036. con = ldap.initialize(self.auth_ldap_server)
  1037. con.set_option(ldap.OPT_REFERRALS, 0)
  1038. if self.auth_ldap_use_tls:
  1039. try:
  1040. con.start_tls_s()
  1041. except Exception:
  1042. log.error(LOGMSG_ERR_SEC_AUTH_LDAP_TLS, self.auth_ldap_server)
  1043. return None
  1044. # Define variables, so we can check if they are set in later steps
  1045. user_dn = None
  1046. user_attributes = {}
  1047. # Flow 1 - (Indirect Search Bind):
  1048. # - in this flow, special bind credentials are used to perform the
  1049. # LDAP search
  1050. # - in this flow, AUTH_LDAP_SEARCH must be set
  1051. if self.auth_ldap_bind_user:
  1052. # Bind with AUTH_LDAP_BIND_USER/AUTH_LDAP_BIND_PASSWORD
  1053. # (authorizes for LDAP search)
  1054. self._ldap_bind_indirect(ldap, con)
  1055. # Search for `username`
  1056. # - returns the `user_dn` needed for binding to validate credentials
  1057. # - returns the `user_attributes` needed for
  1058. # AUTH_USER_REGISTRATION/AUTH_ROLES_SYNC_AT_LOGIN
  1059. if self.auth_ldap_search:
  1060. user_dn, user_attributes = self._search_ldap(ldap, con, username)
  1061. else:
  1062. log.error(
  1063. "AUTH_LDAP_SEARCH must be set when using AUTH_LDAP_BIND_USER"
  1064. )
  1065. return None
  1066. # If search failed, go away
  1067. if user_dn is None:
  1068. log.info(LOGMSG_WAR_SEC_NOLDAP_OBJ, username)
  1069. return None
  1070. # Bind with user_dn/password (validates credentials)
  1071. if not self._ldap_bind(ldap, con, user_dn, password):
  1072. if user:
  1073. self.update_user_auth_stat(user, False)
  1074. # Invalid credentials, go away
  1075. log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
  1076. return None
  1077. # Flow 2 - (Direct Search Bind):
  1078. # - in this flow, the credentials provided by the end-user are used
  1079. # to perform the LDAP search
  1080. # - in this flow, we only search LDAP if AUTH_LDAP_SEARCH is set
  1081. # - features like AUTH_USER_REGISTRATION & AUTH_ROLES_SYNC_AT_LOGIN
  1082. # will only work if AUTH_LDAP_SEARCH is set
  1083. else:
  1084. # Copy the provided username (so we can apply formatters)
  1085. bind_username = username
  1086. # update `bind_username` by applying AUTH_LDAP_APPEND_DOMAIN
  1087. # - for Microsoft AD, which allows binding with userPrincipalName
  1088. if self.auth_ldap_append_domain:
  1089. bind_username = bind_username + "@" + self.auth_ldap_append_domain
  1090. # Update `bind_username` by applying AUTH_LDAP_USERNAME_FORMAT
  1091. # - for transforming the username into a DN,
  1092. # for example: "uid=%s,ou=example,o=test"
  1093. if self.auth_ldap_username_format:
  1094. bind_username = self.auth_ldap_username_format % bind_username
  1095. # Bind with bind_username/password
  1096. # (validates credentials & authorizes for LDAP search)
  1097. if not self._ldap_bind(ldap, con, bind_username, password):
  1098. if user:
  1099. self.update_user_auth_stat(user, False)
  1100. # Invalid credentials, go away
  1101. log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, bind_username)
  1102. return None
  1103. # Search for `username` (if AUTH_LDAP_SEARCH is set)
  1104. # - returns the `user_attributes`
  1105. # needed for AUTH_USER_REGISTRATION/AUTH_ROLES_SYNC_AT_LOGIN
  1106. # - we search on `username` not `bind_username`,
  1107. # because AUTH_LDAP_APPEND_DOMAIN and AUTH_LDAP_USERNAME_FORMAT
  1108. # would result in an invalid search filter
  1109. if self.auth_ldap_search:
  1110. user_dn, user_attributes = self._search_ldap(ldap, con, username)
  1111. # If search failed, go away
  1112. if user_dn is None:
  1113. log.info(LOGMSG_WAR_SEC_NOLDAP_OBJ, username)
  1114. return None
  1115. # Sync the user's roles
  1116. if user and user_attributes and self.auth_roles_sync_at_login:
  1117. user.roles = self._ldap_calculate_user_roles(user_attributes)
  1118. log.debug(
  1119. "Calculated new roles for user='%s' as: %s", user_dn, user.roles
  1120. )
  1121. # If the user is new, register them
  1122. if (not user) and user_attributes and self.auth_user_registration:
  1123. user = self.add_user(
  1124. username=username,
  1125. first_name=self.ldap_extract(
  1126. user_attributes, self.auth_ldap_firstname_field, ""
  1127. ),
  1128. last_name=self.ldap_extract(
  1129. user_attributes, self.auth_ldap_lastname_field, ""
  1130. ),
  1131. email=self.ldap_extract(
  1132. user_attributes,
  1133. self.auth_ldap_email_field,
  1134. f"{username}@email.notfound",
  1135. ),
  1136. role=self._ldap_calculate_user_roles(user_attributes),
  1137. )
  1138. log.debug("New user registered: %s", user)
  1139. # If user registration failed, go away
  1140. if not user:
  1141. log.info(LOGMSG_ERR_SEC_ADD_REGISTER_USER, username)
  1142. return None
  1143. # LOGIN SUCCESS (only if user is now registered)
  1144. if user:
  1145. self.update_user_auth_stat(user)
  1146. return user
  1147. else:
  1148. return None
  1149. except ldap.LDAPError as e:
  1150. msg = None
  1151. if isinstance(e, dict):
  1152. msg = getattr(e, "message", None)
  1153. if (msg is not None) and ("desc" in msg):
  1154. log.error(LOGMSG_ERR_SEC_AUTH_LDAP, e.message["desc"])
  1155. return None
  1156. else:
  1157. log.error(e)
  1158. return None
  1159. def auth_user_oid(self, email):
  1160. """
  1161. OpenID user Authentication
  1162. :param email: user's email to authenticate
  1163. :type self: User model
  1164. """
  1165. user = self.find_user(email=email)
  1166. if user is None or (not user.is_active):
  1167. log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, email)
  1168. return None
  1169. else:
  1170. self.update_user_auth_stat(user)
  1171. return user
  1172. def auth_user_remote_user(self, username):
  1173. """
  1174. REMOTE_USER user Authentication
  1175. :param username: user's username for remote auth
  1176. :type self: User model
  1177. """
  1178. user = self.find_user(username=username)
  1179. # User does not exist, create one if auto user registration.
  1180. if user is None and self.auth_user_registration:
  1181. user = self.add_user(
  1182. # All we have is REMOTE_USER, so we set
  1183. # the other fields to blank.
  1184. username=username,
  1185. first_name=username,
  1186. last_name="-",
  1187. email=username + "@email.notfound",
  1188. role=self.find_role(self.auth_user_registration_role),
  1189. )
  1190. # If user does not exist on the DB and not auto user registration,
  1191. # or user is inactive, go away.
  1192. elif user is None or (not user.is_active):
  1193. log.info(LOGMSG_WAR_SEC_LOGIN_FAILED, username)
  1194. return None
  1195. self.update_user_auth_stat(user)
  1196. return user
  1197. def _oauth_calculate_user_roles(self, userinfo) -> List[str]:
  1198. user_role_objects = set()
  1199. # apply AUTH_ROLES_MAPPING
  1200. if len(self.auth_roles_mapping) > 0:
  1201. user_role_keys = userinfo.get("role_keys", [])
  1202. user_role_objects.update(self.get_roles_from_keys(user_role_keys))
  1203. # apply AUTH_USER_REGISTRATION_ROLE
  1204. if self.auth_user_registration:
  1205. registration_role_name = self.auth_user_registration_role
  1206. # if AUTH_USER_REGISTRATION_ROLE_JMESPATH is set,
  1207. # use it for the registration role
  1208. if self.auth_user_registration_role_jmespath:
  1209. import jmespath
  1210. registration_role_name = jmespath.search(
  1211. self.auth_user_registration_role_jmespath, userinfo
  1212. )
  1213. # lookup registration role in flask db
  1214. fab_role = self.find_role(registration_role_name)
  1215. if fab_role:
  1216. user_role_objects.add(fab_role)
  1217. else:
  1218. log.warning(
  1219. "Can't find AUTH_USER_REGISTRATION role: %s", registration_role_name
  1220. )
  1221. return list(user_role_objects)
  1222. def auth_user_oauth(self, userinfo):
  1223. """
  1224. Method for authenticating user with OAuth.
  1225. :userinfo: dict with user information
  1226. (keys are the same as User model columns)
  1227. """
  1228. # extract the username from `userinfo`
  1229. if "username" in userinfo:
  1230. username = userinfo["username"]
  1231. elif "email" in userinfo:
  1232. username = userinfo["email"]
  1233. else:
  1234. log.error("OAUTH userinfo does not have username or email %s", userinfo)
  1235. return None
  1236. # If username is empty, go away
  1237. if (username is None) or username == "":
  1238. return None
  1239. # Search the DB for this user
  1240. user = self.find_user(username=username)
  1241. # If user is not active, go away
  1242. if user and (not user.is_active):
  1243. return None
  1244. # If user is not registered, and not self-registration, go away
  1245. if (not user) and (not self.auth_user_registration):
  1246. return None
  1247. # Sync the user's roles
  1248. if user and self.auth_roles_sync_at_login:
  1249. user.roles = self._oauth_calculate_user_roles(userinfo)
  1250. log.debug("Calculated new roles for user='%s' as: %s", username, user.roles)
  1251. # If the user is new, register them
  1252. if (not user) and self.auth_user_registration:
  1253. user = self.add_user(
  1254. username=username,
  1255. first_name=userinfo.get("first_name", ""),
  1256. last_name=userinfo.get("last_name", ""),
  1257. email=userinfo.get("email", "") or f"{username}@email.notfound",
  1258. role=self._oauth_calculate_user_roles(userinfo),
  1259. )
  1260. log.debug("New user registered: %s", user)
  1261. # If user registration failed, go away
  1262. if not user:
  1263. log.error("Error creating a new OAuth user %s", username)
  1264. return None
  1265. # LOGIN SUCCESS (only if user is now registered)
  1266. if user:
  1267. self.update_user_auth_stat(user)
  1268. return user
  1269. else:
  1270. return None
  1271. """
  1272. ----------------------------------------
  1273. PERMISSION ACCESS CHECK
  1274. ----------------------------------------
  1275. """
  1276. def is_item_public(self, permission_name, view_name):
  1277. """
  1278. Check if view has public permissions
  1279. :param permission_name:
  1280. the permission: can_show, can_edit...
  1281. :param view_name:
  1282. the name of the class view (child of BaseView)
  1283. """
  1284. permissions = self.get_public_permissions()
  1285. if permissions:
  1286. for i in permissions:
  1287. if (view_name == i.view_menu.name) and (
  1288. permission_name == i.permission.name
  1289. ):
  1290. return True
  1291. return False
  1292. else:
  1293. return False
  1294. def _has_access_builtin_roles(
  1295. self, role, permission_name: str, view_name: str
  1296. ) -> bool:
  1297. """
  1298. Checks permission on builtin role
  1299. """
  1300. builtin_pvms = self.builtin_roles.get(role.name, [])
  1301. for pvm in builtin_pvms:
  1302. _view_name = pvm[0]
  1303. _permission_name = pvm[1]
  1304. if re.match(_view_name, view_name) and re.match(
  1305. _permission_name, permission_name
  1306. ):
  1307. return True
  1308. return False
  1309. def _has_view_access(
  1310. self, user: object, permission_name: str, view_name: str
  1311. ) -> bool:
  1312. roles = user.roles
  1313. db_role_ids = list()
  1314. # First check against builtin (statically configured) roles
  1315. # because no database query is needed
  1316. for role in roles:
  1317. if role.name in self.builtin_roles:
  1318. if self._has_access_builtin_roles(role, permission_name, view_name):
  1319. return True
  1320. else:
  1321. db_role_ids.append(role.id)
  1322. # If it's not a builtin role check against database store roles
  1323. return self.exist_permission_on_roles(view_name, permission_name, db_role_ids)
  1324. def get_oid_identity_url(self, provider_name: str) -> Optional[str]:
  1325. """
  1326. Returns the OIDC identity provider URL
  1327. """
  1328. for provider in self.openid_providers:
  1329. if provider.get("name") == provider_name:
  1330. return provider.get("url")
  1331. def get_user_roles(self, user) -> List[object]:
  1332. """
  1333. Get current user roles, if user is not authenticated returns the public role
  1334. """
  1335. if not user.is_authenticated:
  1336. return [self.get_public_role()]
  1337. return user.roles
  1338. def get_user_roles_permissions(self, user) -> Dict[str, List[Tuple[str, str]]]:
  1339. """
  1340. Utility method just implemented for SQLAlchemy.
  1341. Take a look to: flask_appbuilder.security.sqla.manager
  1342. :param user:
  1343. :return:
  1344. """
  1345. raise NotImplementedError()
  1346. def get_role_permissions(self, role) -> Set[Tuple[str, str]]:
  1347. """
  1348. Get all permissions for a certain role
  1349. """
  1350. result = set()
  1351. if role.name in self.builtin_roles:
  1352. for permission in self.builtin_roles[role.name]:
  1353. result.add((permission[1], permission[0]))
  1354. else:
  1355. for permission in self.get_db_role_permissions(role.id):
  1356. result.add((permission.permission.name, permission.view_menu.name))
  1357. return result
  1358. def get_user_permissions(self, user) -> Set[Tuple[str, str]]:
  1359. """
  1360. Get all permissions from the current user
  1361. """
  1362. roles = self.get_user_roles(user)
  1363. result = set()
  1364. for role in roles:
  1365. result.update(self.get_role_permissions(role))
  1366. return result
  1367. def _get_user_permission_view_menus(
  1368. self, user: object, permission_name: str, view_menus_name: List[str]
  1369. ) -> Set[str]:
  1370. """
  1371. Return a set of view menu names with a certain permission name
  1372. that a user has access to. Mainly used to fetch all menu permissions
  1373. on a single db call, will also check public permissions and builtin roles
  1374. """
  1375. db_role_ids = list()
  1376. if user is None:
  1377. # include public role
  1378. roles = [self.get_public_role()]
  1379. else:
  1380. roles = user.roles
  1381. # First check against builtin (statically configured) roles
  1382. # because no database query is needed
  1383. result = set()
  1384. for role in roles:
  1385. if role.name in self.builtin_roles:
  1386. for view_menu_name in view_menus_name:
  1387. if self._has_access_builtin_roles(
  1388. role, permission_name, view_menu_name
  1389. ):
  1390. result.add(view_menu_name)
  1391. else:
  1392. db_role_ids.append(role.id)
  1393. # Then check against database-stored roles
  1394. pvms_names = [
  1395. pvm.view_menu.name
  1396. for pvm in self.find_roles_permission_view_menus(
  1397. permission_name, db_role_ids
  1398. )
  1399. ]
  1400. result.update(pvms_names)
  1401. return result
  1402. def has_access(self, permission_name: str, view_name: str) -> bool:
  1403. """
  1404. Check if current user or public has access to view or menu
  1405. """
  1406. if current_user.is_authenticated:
  1407. return self._has_view_access(g.user, permission_name, view_name)
  1408. elif current_user_jwt:
  1409. return self._has_view_access(current_user_jwt, permission_name, view_name)
  1410. else:
  1411. return self.is_item_public(permission_name, view_name)
  1412. def get_user_menu_access(self, menu_names: List[str] = None) -> Set[str]:
  1413. if current_user.is_authenticated:
  1414. return self._get_user_permission_view_menus(
  1415. g.user, "menu_access", view_menus_name=menu_names
  1416. )
  1417. elif current_user_jwt:
  1418. return self._get_user_permission_view_menus(
  1419. current_user_jwt, "menu_access", view_menus_name=menu_names
  1420. )
  1421. else:
  1422. return self._get_user_permission_view_menus(
  1423. None, "menu_access", view_menus_name=menu_names
  1424. )
  1425. def add_limit_view(self, baseview):
  1426. if not baseview.limits:
  1427. return
  1428. for limit in baseview.limits:
  1429. self.limiter.limit(
  1430. limit_value=limit.limit_value,
  1431. key_func=limit.key_func,
  1432. per_method=limit.per_method,
  1433. methods=limit.methods,
  1434. error_message=limit.error_message,
  1435. exempt_when=limit.exempt_when,
  1436. override_defaults=limit.override_defaults,
  1437. deduct_when=limit.deduct_when,
  1438. on_breach=limit.on_breach,
  1439. cost=limit.cost,
  1440. )(baseview.blueprint)
  1441. def add_permissions_view(self, base_permissions, view_menu):
  1442. """
  1443. Adds a permission on a view menu to the backend
  1444. :param base_permissions:
  1445. list of permissions from view (all exposed methods):
  1446. 'can_add','can_edit' etc...
  1447. :param view_menu:
  1448. name of the view or menu to add
  1449. """
  1450. view_menu_db = self.add_view_menu(view_menu)
  1451. perm_views = self.find_permissions_view_menu(view_menu_db)
  1452. if not perm_views:
  1453. # No permissions yet on this view
  1454. for permission in base_permissions:
  1455. pv = self.add_permission_view_menu(permission, view_menu)
  1456. if self.auth_role_admin not in self.builtin_roles:
  1457. role_admin = self.find_role(self.auth_role_admin)
  1458. self.add_permission_role(role_admin, pv)
  1459. else:
  1460. # Permissions on this view exist but....
  1461. role_admin = self.find_role(self.auth_role_admin)
  1462. for permission in base_permissions:
  1463. # Check if base view permissions exist
  1464. if not self.exist_permission_on_views(perm_views, permission):
  1465. pv = self.add_permission_view_menu(permission, view_menu)
  1466. if self.auth_role_admin not in self.builtin_roles:
  1467. self.add_permission_role(role_admin, pv)
  1468. for perm_view in perm_views:
  1469. if perm_view.permission is None:
  1470. # Skip this perm_view, it has a null permission
  1471. continue
  1472. if perm_view.permission.name not in base_permissions:
  1473. # perm to delete
  1474. roles = self.get_all_roles()
  1475. perm = self.find_permission(perm_view.permission.name)
  1476. # del permission from all roles
  1477. for role in roles:
  1478. self.del_permission_role(role, perm)
  1479. self.del_permission_view_menu(perm_view.permission.name, view_menu)
  1480. elif (
  1481. self.auth_role_admin not in self.builtin_roles
  1482. and perm_view not in role_admin.permissions
  1483. ):
  1484. # Role Admin must have all permissions
  1485. self.add_permission_role(role_admin, perm_view)
  1486. def add_permissions_menu(self, view_menu_name):
  1487. """
  1488. Adds menu_access to menu on permission_view_menu
  1489. :param view_menu_name:
  1490. The menu name
  1491. """
  1492. self.add_view_menu(view_menu_name)
  1493. pv = self.find_permission_view_menu("menu_access", view_menu_name)
  1494. if not pv:
  1495. pv = self.add_permission_view_menu("menu_access", view_menu_name)
  1496. if self.auth_role_admin not in self.builtin_roles:
  1497. role_admin = self.find_role(self.auth_role_admin)
  1498. self.add_permission_role(role_admin, pv)
  1499. def security_cleanup(self, baseviews, menus):
  1500. """
  1501. Will cleanup all unused permissions from the database
  1502. :param baseviews: A list of BaseViews class
  1503. :param menus: Menu class
  1504. """
  1505. viewsmenus = self.get_all_view_menu()
  1506. roles = self.get_all_roles()
  1507. for viewmenu in viewsmenus:
  1508. found = False
  1509. for baseview in baseviews:
  1510. if viewmenu.name == baseview.class_permission_name:
  1511. found = True
  1512. break
  1513. if menus.find(viewmenu.name):
  1514. found = True
  1515. if not found:
  1516. permissions = self.find_permissions_view_menu(viewmenu)
  1517. for permission in permissions:
  1518. for role in roles:
  1519. self.del_permission_role(role, permission)
  1520. self.del_permission_view_menu(
  1521. permission.permission.name, viewmenu.name
  1522. )
  1523. self.del_view_menu(viewmenu.name)
  1524. self.security_converge(baseviews, menus)
  1525. @staticmethod
  1526. def _get_new_old_permissions(baseview) -> Dict:
  1527. ret = dict()
  1528. for method_name, permission_name in baseview.method_permission_name.items():
  1529. old_permission_name = baseview.previous_method_permission_name.get(
  1530. method_name
  1531. )
  1532. # Actions do not get prefix when normally defined
  1533. if hasattr(baseview, "actions") and baseview.actions.get(
  1534. old_permission_name
  1535. ):
  1536. permission_prefix = ""
  1537. else:
  1538. permission_prefix = PERMISSION_PREFIX
  1539. if old_permission_name:
  1540. if PERMISSION_PREFIX + permission_name not in ret:
  1541. ret[PERMISSION_PREFIX + permission_name] = {
  1542. permission_prefix + old_permission_name
  1543. }
  1544. else:
  1545. ret[PERMISSION_PREFIX + permission_name].add(
  1546. permission_prefix + old_permission_name
  1547. )
  1548. return ret
  1549. @staticmethod
  1550. def _add_state_transition(
  1551. state_transition: Dict,
  1552. old_view_name: str,
  1553. old_perm_name: str,
  1554. view_name: str,
  1555. perm_name: str,
  1556. ) -> None:
  1557. old_pvm = state_transition["add"].get((old_view_name, old_perm_name))
  1558. if old_pvm:
  1559. state_transition["add"][(old_view_name, old_perm_name)].add(
  1560. (view_name, perm_name)
  1561. )
  1562. else:
  1563. state_transition["add"][(old_view_name, old_perm_name)] = {
  1564. (view_name, perm_name)
  1565. }
  1566. state_transition["del_role_pvm"].add((old_view_name, old_perm_name))
  1567. state_transition["del_views"].add(old_view_name)
  1568. state_transition["del_perms"].add(old_perm_name)
  1569. @staticmethod
  1570. def _update_del_transitions(state_transitions: Dict, baseviews: List) -> None:
  1571. """
  1572. Mutates state_transitions, loop baseviews and prunes all
  1573. views and permissions that are not to delete because references
  1574. exist.
  1575. :param baseview:
  1576. :param state_transitions:
  1577. :return:
  1578. """
  1579. for baseview in baseviews:
  1580. state_transitions["del_views"].discard(baseview.class_permission_name)
  1581. for permission in baseview.base_permissions:
  1582. state_transitions["del_role_pvm"].discard(
  1583. (baseview.class_permission_name, permission)
  1584. )
  1585. state_transitions["del_perms"].discard(permission)
  1586. def create_state_transitions(
  1587. self, baseviews: List, menus: Optional[List[Any]]
  1588. ) -> Dict:
  1589. """
  1590. Creates a Dict with all the necessary vm/permission transitions
  1591. Dict: {
  1592. "add": {(<VM>, <PERM>): ((<VM>, PERM), ... )}
  1593. "del_role_pvm": ((<VM>, <PERM>), ...)
  1594. "del_views": (<VM>, ... )
  1595. "del_perms": (<PERM>, ... )
  1596. }
  1597. :param baseviews: List with all the registered BaseView, BaseApi
  1598. :param menus: List with all the menu entries
  1599. :return: Dict with state transitions
  1600. """
  1601. state_transitions = {
  1602. "add": {},
  1603. "del_role_pvm": set(),
  1604. "del_views": set(),
  1605. "del_perms": set(),
  1606. }
  1607. for baseview in baseviews:
  1608. add_all_flag = False
  1609. new_view_name = baseview.class_permission_name
  1610. permission_mapping = self._get_new_old_permissions(baseview)
  1611. if baseview.previous_class_permission_name:
  1612. old_view_name = baseview.previous_class_permission_name
  1613. add_all_flag = True
  1614. else:
  1615. new_view_name = baseview.class_permission_name
  1616. old_view_name = new_view_name
  1617. for new_perm_name in baseview.base_permissions:
  1618. if add_all_flag:
  1619. old_perm_names = permission_mapping.get(new_perm_name)
  1620. old_perm_names = old_perm_names or (new_perm_name,)
  1621. for old_perm_name in old_perm_names:
  1622. self._add_state_transition(
  1623. state_transitions,
  1624. old_view_name,
  1625. old_perm_name,
  1626. new_view_name,
  1627. new_perm_name,
  1628. )
  1629. else:
  1630. old_perm_names = permission_mapping.get(new_perm_name) or set()
  1631. for old_perm_name in old_perm_names:
  1632. self._add_state_transition(
  1633. state_transitions,
  1634. old_view_name,
  1635. old_perm_name,
  1636. new_view_name,
  1637. new_perm_name,
  1638. )
  1639. self._update_del_transitions(state_transitions, baseviews)
  1640. return state_transitions
  1641. def security_converge(
  1642. self, baseviews: List, menus: Optional[List[Any]], dry=False
  1643. ) -> Dict:
  1644. """
  1645. Converges overridden permissions on all registered views/api
  1646. will compute all necessary operations from `class_permissions_name`,
  1647. `previous_class_permission_name`, method_permission_name`,
  1648. `previous_method_permission_name` class attributes.
  1649. :param baseviews: List of registered views/apis
  1650. :param menus: List of menu items
  1651. :param dry: If True will not change DB
  1652. :return: Dict with the necessary operations (state_transitions)
  1653. """
  1654. state_transitions = self.create_state_transitions(baseviews, menus)
  1655. if dry:
  1656. return state_transitions
  1657. if not state_transitions:
  1658. log.info("No state transitions found")
  1659. return dict()
  1660. log.debug("State transitions: %s", state_transitions)
  1661. roles = self.get_all_roles()
  1662. for role in roles:
  1663. permissions = list(role.permissions)
  1664. for pvm in permissions:
  1665. new_pvm_states = state_transitions["add"].get(
  1666. (pvm.view_menu.name, pvm.permission.name)
  1667. )
  1668. if not new_pvm_states:
  1669. continue
  1670. for new_pvm_state in new_pvm_states:
  1671. new_pvm = self.add_permission_view_menu(
  1672. new_pvm_state[1], new_pvm_state[0]
  1673. )
  1674. self.add_permission_role(role, new_pvm)
  1675. if (pvm.view_menu.name, pvm.permission.name) in state_transitions[
  1676. "del_role_pvm"
  1677. ]:
  1678. self.del_permission_role(role, pvm)
  1679. for pvm in state_transitions["del_role_pvm"]:
  1680. self.del_permission_view_menu(pvm[1], pvm[0], cascade=False)
  1681. for view_name in state_transitions["del_views"]:
  1682. self.del_view_menu(view_name)
  1683. for permission_name in state_transitions["del_perms"]:
  1684. self.del_permission(permission_name)
  1685. return state_transitions
  1686. """
  1687. ---------------------------
  1688. INTERFACE ABSTRACT METHODS
  1689. ---------------------------
  1690. ---------------------
  1691. PRIMITIVES FOR USERS
  1692. ----------------------
  1693. """
  1694. def find_register_user(self, registration_hash):
  1695. """
  1696. Generic function to return user registration
  1697. """
  1698. raise NotImplementedError
  1699. def add_register_user(
  1700. self, username, first_name, last_name, email, password="", hashed_password=""
  1701. ):
  1702. """
  1703. Generic function to add user registration
  1704. """
  1705. raise NotImplementedError
  1706. def del_register_user(self, register_user):
  1707. """
  1708. Generic function to delete user registration
  1709. """
  1710. raise NotImplementedError
  1711. def get_user_by_id(self, pk):
  1712. """
  1713. Generic function to return user by it's id (pk)
  1714. """
  1715. raise NotImplementedError
  1716. def find_user(self, username=None, email=None):
  1717. """
  1718. Generic function find a user by it's username or email
  1719. """
  1720. raise NotImplementedError
  1721. def get_all_users(self):
  1722. """
  1723. Generic function that returns all existing users
  1724. """
  1725. raise NotImplementedError
  1726. def get_db_role_permissions(self, role_id: int) -> List[object]:
  1727. """
  1728. Get all DB permissions from a role id
  1729. """
  1730. raise NotImplementedError
  1731. def add_user(self, username, first_name, last_name, email, role, password=""):
  1732. """
  1733. Generic function to create user
  1734. """
  1735. raise NotImplementedError
  1736. def update_user(self, user):
  1737. """
  1738. Generic function to update user
  1739. :param user: User model to update to database
  1740. """
  1741. raise NotImplementedError
  1742. def count_users(self):
  1743. """
  1744. Generic function to count the existing users
  1745. """
  1746. raise NotImplementedError
  1747. """
  1748. ----------------------
  1749. PRIMITIVES FOR ROLES
  1750. ----------------------
  1751. """
  1752. def find_role(self, name):
  1753. raise NotImplementedError
  1754. def add_role(self, name, permissions=None):
  1755. raise NotImplementedError
  1756. def update_role(self, pk, name):
  1757. raise NotImplementedError
  1758. def get_all_roles(self):
  1759. raise NotImplementedError
  1760. """
  1761. ----------------------------
  1762. PRIMITIVES FOR PERMISSIONS
  1763. ----------------------------
  1764. """
  1765. def get_public_role(self):
  1766. """
  1767. returns all permissions from public role
  1768. """
  1769. raise NotImplementedError
  1770. def get_public_permissions(self):
  1771. """
  1772. returns all permissions from public role
  1773. """
  1774. raise NotImplementedError
  1775. def find_permission(self, name):
  1776. """
  1777. Finds and returns a Permission by name
  1778. """
  1779. raise NotImplementedError
  1780. def find_roles_permission_view_menus(
  1781. self, permission_name: str, role_ids: List[int]
  1782. ):
  1783. raise NotImplementedError
  1784. def exist_permission_on_roles(
  1785. self, view_name: str, permission_name: str, role_ids: List[int]
  1786. ) -> bool:
  1787. """
  1788. Finds and returns permission views for a group of roles
  1789. """
  1790. raise NotImplementedError
  1791. def add_permission(self, name):
  1792. """
  1793. Adds a permission to the backend, model permission
  1794. :param name:
  1795. name of the permission: 'can_add','can_edit' etc...
  1796. """
  1797. raise NotImplementedError
  1798. def del_permission(self, name):
  1799. """
  1800. Deletes a permission from the backend, model permission
  1801. :param name:
  1802. name of the permission: 'can_add','can_edit' etc...
  1803. """
  1804. raise NotImplementedError
  1805. """
  1806. ----------------------
  1807. PRIMITIVES VIEW MENU
  1808. ----------------------
  1809. """
  1810. def find_view_menu(self, name):
  1811. """
  1812. Finds and returns a ViewMenu by name
  1813. """
  1814. raise NotImplementedError
  1815. def get_all_view_menu(self):
  1816. raise NotImplementedError
  1817. def add_view_menu(self, name):
  1818. """
  1819. Adds a view or menu to the backend, model view_menu
  1820. param name:
  1821. name of the view menu to add
  1822. """
  1823. raise NotImplementedError
  1824. def del_view_menu(self, name):
  1825. """
  1826. Deletes a ViewMenu from the backend
  1827. :param name:
  1828. name of the ViewMenu
  1829. """
  1830. raise NotImplementedError
  1831. """
  1832. ----------------------
  1833. PERMISSION VIEW MENU
  1834. ----------------------
  1835. """
  1836. def find_permission_view_menu(self, permission_name, view_menu_name):
  1837. """
  1838. Finds and returns a PermissionView by names
  1839. """
  1840. raise NotImplementedError
  1841. def find_permissions_view_menu(self, view_menu):
  1842. """
  1843. Finds all permissions from ViewMenu, returns list of PermissionView
  1844. :param view_menu: ViewMenu object
  1845. :return: list of PermissionView objects
  1846. """
  1847. raise NotImplementedError
  1848. def add_permission_view_menu(self, permission_name, view_menu_name):
  1849. """
  1850. Adds a permission on a view or menu to the backend
  1851. :param permission_name:
  1852. name of the permission to add: 'can_add','can_edit' etc...
  1853. :param view_menu_name:
  1854. name of the view menu to add
  1855. """
  1856. raise NotImplementedError
  1857. def del_permission_view_menu(self, permission_name, view_menu_name, cascade=True):
  1858. raise NotImplementedError
  1859. def exist_permission_on_views(self, lst, item):
  1860. raise NotImplementedError
  1861. def exist_permission_on_view(self, lst, permission, view_menu):
  1862. raise NotImplementedError
  1863. def add_permission_role(self, role, perm_view):
  1864. """
  1865. Add permission-ViewMenu object to Role
  1866. :param role:
  1867. The role object
  1868. :param perm_view:
  1869. The PermissionViewMenu object
  1870. """
  1871. raise NotImplementedError
  1872. def del_permission_role(self, role, perm_view):
  1873. """
  1874. Remove permission-ViewMenu object to Role
  1875. :param role:
  1876. The role object
  1877. :param perm_view:
  1878. The PermissionViewMenu object
  1879. """
  1880. raise NotImplementedError
  1881. def export_roles(
  1882. self, path: Optional[str] = None, indent: Optional[Union[int, str]] = None
  1883. ) -> None:
  1884. """Exports roles to JSON file."""
  1885. raise NotImplementedError
  1886. def import_roles(self, path: str) -> None:
  1887. """Imports roles from JSON file."""
  1888. raise NotImplementedError
  1889. def load_user(self, pk):
  1890. user = self.get_user_by_id(int(pk))
  1891. if user.is_active:
  1892. return user
  1893. def load_user_jwt(self, _jwt_header, jwt_data):
  1894. identity = jwt_data["sub"]
  1895. user = self.load_user(identity)
  1896. if user.is_active:
  1897. # Set flask g.user to JWT user, we can't do it on before request
  1898. g.user = user
  1899. return user
  1900. @staticmethod
  1901. def before_request():
  1902. g.user = current_user