PageRenderTime 105ms CodeModel.GetById 40ms RepoModel.GetById 0ms app.codeStats 0ms

/linotpd/src/linotp/lib/base.py

https://gitlab.com/sheshanarayanag/LinOTP
Python | 542 lines | 499 code | 7 blank | 36 comment | 4 complexity | 282263d3371468324be8a7d504ecfd59 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. #
  3. # LinOTP - the open source solution for two factor authentication
  4. # Copyright (C) 2010 - 2016 LSE Leading Security Experts GmbH
  5. #
  6. # This file is part of LinOTP server.
  7. #
  8. # This program is free software: you can redistribute it and/or
  9. # modify it under the terms of the GNU Affero General Public
  10. # License, version 3, as published by the Free Software Foundation.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Affero General Public License for more details.
  16. #
  17. # You should have received a copy of the
  18. # GNU Affero General Public License
  19. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. #
  21. #
  22. # E-mail: linotp@lsexperts.de
  23. # Contact: www.linotp.org
  24. # Support: www.lsexperts.de
  25. #
  26. '''The Controller's Base class '''
  27. import os
  28. import re
  29. from pylons.i18n.translation import _ as translate
  30. from pylons.i18n.translation import set_lang
  31. from pylons.i18n import LanguageError
  32. from pylons.controllers import WSGIController
  33. from pylons import tmpl_context as c
  34. from pylons import config
  35. from pylons import request
  36. from linotp.lib.config import initLinotpConfig
  37. from linotp.lib.resolver import initResolvers
  38. from linotp.lib.resolver import setupResolvers
  39. from linotp.lib.resolver import closeResolvers
  40. from linotp.lib.user import getUserFromRequest
  41. from linotp.lib.user import getUserFromParam
  42. from linotp.lib.realm import getDefaultRealm
  43. from linotp.lib.realm import getRealms
  44. from linotp.lib.config import getGlobalObject
  45. from linotp.lib.crypt import init_qrtoken_secret_key
  46. from linotp.model import meta
  47. from linotp.lib.openid import SQLStorage
  48. from linotp.lib.context import request_context
  49. from linotp.lib.context import request_context_safety
  50. # this is a hack for the static code analyser, which
  51. # would otherwise show session.close() as error
  52. import linotp.model.meta
  53. Session = linotp.model.meta.Session
  54. from linotp.lib.config import getLinotpConfig
  55. from linotp.lib.policy import getPolicies
  56. from linotp.lib.util import get_client
  57. import logging
  58. log = logging.getLogger(__name__)
  59. Audit = config.get('audit')
  60. # HTTP-ACCEPT-LANGUAGE strings are in the form of i.e.
  61. # de-DE, de; q=0.7, en; q=0.3
  62. accept_language_regexp = re.compile(r'\s*([^\s;,]+)\s*[;\s*q=[0-9.]*]?\s*,?')
  63. def set_config(key, value, typ, description=None):
  64. '''
  65. create an intial config entry, if it does not exist
  66. :param key: the key
  67. :param value: the value
  68. :param description: the description of the key
  69. :return: nothing
  70. '''
  71. count = Session.query(linotp.model.Config).filter(
  72. linotp.model.Config.Key == "linotp." + key).count()
  73. if count == 0:
  74. config_entry = linotp.model.Config(key, value,
  75. Type=typ, Description=description)
  76. Session.add(config_entry)
  77. return
  78. def set_defaults():
  79. '''
  80. add linotp default config settings
  81. :return: - nothing -
  82. '''
  83. log.info("Adding config default data...")
  84. set_config(key=u"DefaultMaxFailCount",
  85. value=u"10", typ=u"int",
  86. description=u"The default maximum count for unsuccessful logins")
  87. set_config(key=u"DefaultCountWindow",
  88. value=u"10", typ=u"int",
  89. description=u"The default lookup window for tokens out of sync ")
  90. set_config(key=u"DefaultSyncWindow",
  91. value=u"1000", typ=u"int",
  92. description=u"The default lookup window for tokens out of sync ")
  93. set_config(key=u"DefaultChallengeValidityTime",
  94. value=u"120", typ=u"int",
  95. description=u"The default time, a challenge is regarded as valid.")
  96. set_config(key=u"DefaultResetFailCount",
  97. value=u"True", typ=u"bool",
  98. description=u"The default maximum count for unsucessful logins")
  99. set_config(key=u"DefaultOtpLen",
  100. value=u"6", typ=u"int",
  101. description=u"The default len of the otp values")
  102. set_config(key=u"QRTokenOtpLen",
  103. value=u"8", typ=u"int",
  104. description=u"The default len of the otp values")
  105. set_config(key=u"QRChallengeValidityTime",
  106. value=u"150", typ=u"int",
  107. description=u"The default qrtoken time, a challenge is regarded as valid.")
  108. set_config(key=u"QRMaxChallenges",
  109. value=u"4", typ=u"int",
  110. description=u"Maximum open QRToken challenges")
  111. set_config(key=u"PrependPin",
  112. value=u"True", typ=u"bool",
  113. description=u"is the pin prepended - most cases")
  114. set_config(key=u"FailCounterIncOnFalsePin",
  115. value=u"True", typ=u"bool",
  116. description=u"increment the FailCounter, if pin did not match")
  117. set_config(key=u"SMSProvider",
  118. value=u"smsprovider.HttpSMSProvider.HttpSMSProvider", typ=u"text",
  119. description=u"SMS Default Provider via HTTP")
  120. set_config(key=u"SMSProviderTimeout",
  121. value=u"300", typ=u"int",
  122. description=u"Timeout until registration must be done")
  123. set_config(key=u"SMSBlockingTimeout",
  124. value=u"30", typ=u"int",
  125. description=u"Delay until next challenge is created")
  126. set_config(key=u"DefaultBlockingTimeout",
  127. value=u"0", typ=u"int",
  128. description=u"Delay until next challenge is created")
  129. # setup for totp defaults
  130. # "linotp.totp.timeStep";"60";"None";"None"
  131. # "linotp.totp.timeWindow";"600";"None";"None"
  132. # "linotp.totp.timeShift";"240";"None";"None"
  133. set_config(key=u"totp.timeStep",
  134. value=u"30", typ=u"int",
  135. description=u"Time stepping of the time based otp token ")
  136. set_config(key=u"totp.timeWindow",
  137. value=u"300", typ=u"int",
  138. description=u"Lookahead time window of the time based otp token ")
  139. set_config(key=u"totp.timeShift",
  140. value=u"0", typ=u"int",
  141. description=u"Shift between server and totp token")
  142. set_config(key=u"AutoResyncTimeout",
  143. value=u"240", typ=u"int",
  144. description=u"Autosync timeout for an totp token")
  145. # setup for ocra defaults
  146. # OcraDefaultSuite
  147. # QrOcraDefaultSuite
  148. # OcraMaxChallenges
  149. # OcraChallengeTimeout
  150. set_config(key=u"OcraDefaultSuite",
  151. value=u"OCRA-1:HOTP-SHA256-8:C-QN08", typ=u"string",
  152. description=u"Default OCRA suite for an ocra token ")
  153. set_config(key=u"QrOcraDefaultSuite",
  154. value=u"OCRA-1:HOTP-SHA256-8:C-QA64", typ=u"int",
  155. description=u"Default OCRA suite for an ocra token ")
  156. set_config(key=u"OcraMaxChallenges",
  157. value=u"4", typ=u"int",
  158. description=u"Maximum open ocra challenges")
  159. set_config(key=u"OcraChallengeTimeout",
  160. value=u"300", typ=u"int",
  161. description=u"Timeout for an open ocra challenge")
  162. # emailtoken defaults
  163. set_config(key=u"EmailProvider",
  164. value="linotp.provider.emailprovider.SMTPEmailProvider",
  165. typ=u"string",
  166. description=u"Default EmailProvider class")
  167. set_config(key=u"EmailChallengeValidityTime",
  168. value="600", typ=u"int",
  169. description=u"Time that an e-mail token challenge stays valid"
  170. " (seconds)")
  171. set_config(key=u"EmailBlockingTimeout",
  172. value="120", typ=u"int",
  173. description=u"Time during which no new e-mail is sent out")
  174. set_config(key=u'OATHTokenSupport',
  175. value=u"False", typ=u"bool",
  176. description=u"support for hmac token in oath format")
  177. def setup_app(conf, conf_global=None, unitTest=False):
  178. '''
  179. setup_app is the hook, which is called, when the application is created
  180. :param conf: the application configuration
  181. :return: - nothing -
  182. '''
  183. if conf_global is not None:
  184. if conf_global.has_key("sqlalchemy.url"):
  185. log.info("sqlalchemy.url")
  186. else:
  187. conf.get("sqlalchemy.url", None)
  188. if unitTest is True:
  189. log.info("Deleting previous tables...")
  190. meta.metadata.drop_all(bind=meta.engine)
  191. # Create the tables if they don't already exist
  192. log.info("Creating tables...")
  193. meta.metadata.create_all(bind=meta.engine)
  194. if conf.has_key("linotpSecretFile"):
  195. filename = conf.get("linotpSecretFile")
  196. try:
  197. with open(filename):
  198. pass
  199. except IOError:
  200. log.warning("The Linotp Secret File could not be found " +
  201. "-creating a new one: %s" % filename)
  202. f_handle = open(filename, 'ab+')
  203. secret = os.urandom(32 * 5)
  204. f_handle.write(secret)
  205. f_handle.close()
  206. os.chmod(filename, 0400)
  207. log.info("linotpSecretFile: %s" % filename)
  208. set_defaults()
  209. Session.commit()
  210. log.info("Successfully set up.")
  211. class BaseController(WSGIController):
  212. """
  213. BaseController class - will be called with every request
  214. """
  215. def __init__(self, *args, **kw):
  216. """
  217. base controller constructor
  218. :param *args: generic argument array
  219. :param **kw: generic argument dict
  220. :return: None
  221. """
  222. self.sep = None
  223. self.set_language(request.headers)
  224. self.base_auth_user = ''
  225. self.parent = super(WSGIController, self)
  226. self.parent.__init__(*args, **kw)
  227. # make the OpenID SQL Instance globally available
  228. openid_sql = config.get('openid_sql', None)
  229. if openid_sql is None:
  230. try:
  231. openid_storage = SQLStorage()
  232. config['openid_sql'] = openid_storage
  233. except Exception as exx:
  234. config['openid_sql'] = exx
  235. log.error("Failed to configure openid_sql: %r" % exx)
  236. first_run = False
  237. app_setup_done = config.get('app_setup_done', False)
  238. if app_setup_done is False:
  239. try:
  240. setup_app(config)
  241. config['app_setup_done'] = True
  242. first_run = True
  243. except Exception as exx:
  244. config['app_setup_done'] = False
  245. log.error("Failed to serve request: %r" % exx)
  246. raise exx
  247. # set the decryption device before loading linotp config,
  248. # so it contains the decrypted values as well
  249. glo = getGlobalObject()
  250. self.sep = glo.security_provider
  251. try:
  252. hsm = self.sep.getSecurityModule()
  253. self.hsm = hsm
  254. c.hsm = hsm
  255. except Exception as exx:
  256. log.exception('failed to assign hsm device: %r' % exx)
  257. raise exx
  258. l_config = initLinotpConfig()
  259. # initialize the elliptic curve secret + public key for the qrtoken
  260. linotpQrTokenSecretKey = l_config.get('QrTokenSecretKey.system',
  261. False)
  262. if not linotpQrTokenSecretKey:
  263. init_qrtoken_secret_key(l_config, cert_id='system')
  264. resolver_setup_done = config.get('resolver_setup_done', False)
  265. if resolver_setup_done is False:
  266. try:
  267. cache_dir = config.get("app_conf", {}).get("cache_dir", None)
  268. setupResolvers(config=l_config, cache_dir=cache_dir)
  269. config['resolver_setup_done'] = True
  270. except Exception as exx:
  271. config['resolver_setup_done'] = False
  272. log.error("Failed to setup resolver: %r" % exx)
  273. raise exx
  274. # TODO: verify merge dropped
  275. # initResolvers()
  276. # if we are in the setup cycle, we check for the linotpLicenseFile
  277. if first_run:
  278. if "linotpLicenseFile" in config and 'license' not in l_config:
  279. license_str = ''
  280. filename = config.get("linotpLicenseFile", '')
  281. try:
  282. with open(filename) as f:
  283. license_str = f.read()
  284. except IOError:
  285. log.error("linotpLicenseFile: %s" % filename)
  286. if not license_str:
  287. log.error("empty license file: %s" % filename)
  288. else:
  289. with request_context_safety():
  290. request_context['translate'] = translate
  291. import linotp.lib.support
  292. res, msg = linotp.lib.support.setSupportLicense(license_str)
  293. if res is False:
  294. log.error("failed to load license: %s: %s"
  295. % (license_str, msg))
  296. else:
  297. log.info("license successfully loaded")
  298. return
  299. def __call__(self, environ, start_response):
  300. '''Invoke the Controller'''
  301. # WSGIController.__call__ dispatches to the Controller method
  302. # the request is routed to. This routing information is
  303. # available in environ['pylons.routes_dict']
  304. path = ""
  305. with request_context_safety():
  306. self.create_context(request)
  307. try:
  308. if environ:
  309. path = environ.get("PATH_INFO", "") or ""
  310. try:
  311. user_desc = getUserFromRequest(request)
  312. self.base_auth_user = user_desc.get('login', '')
  313. except UnicodeDecodeError as exx:
  314. # we supress Exception here as it will be handled in the
  315. # controller which will return corresponding response
  316. log.info('Failed to identify user due to %r' % exx)
  317. log.debug("request %r" % path)
  318. ret = WSGIController.__call__(self, environ, start_response)
  319. log.debug("reply %r" % ret)
  320. finally:
  321. meta.Session.remove()
  322. # free the lock on the scurityPovider if any
  323. if self.sep:
  324. self.sep.dropSecurityModule()
  325. closeResolvers()
  326. # hint for the garbage collector to make the dishes
  327. data_objects = ["resolvers_loaded", "resolver_types",
  328. "resolver_clazzes", "linotpConfig", "audit", "hsm"]
  329. for data_obj in data_objects:
  330. if hasattr(c, data_obj):
  331. data = getattr(c, data_obj)
  332. del data
  333. log.debug("request %r done!" % path)
  334. return ret
  335. def set_language(self, headers):
  336. '''Invoke before everything else. And set the translation language'''
  337. languages = headers.get('Accept-Language', '')
  338. found_lang = False
  339. for match in accept_language_regexp.finditer(languages):
  340. # make sure we have a correct language code format
  341. language = match.group(1)
  342. if not language:
  343. continue
  344. language = language.replace('_', '-').lower()
  345. # en is the default language
  346. if language.split('-')[0] == 'en':
  347. found_lang = True
  348. break
  349. try:
  350. set_lang(language.split('-')[0])
  351. found_lang = True
  352. break
  353. except LanguageError:
  354. log.debug("Cannot set requested language: %s. Trying next"
  355. " language if available.", language)
  356. if not found_lang and languages:
  357. log.warning("Cannot set preferred language: %r", languages)
  358. return
  359. def create_context(self, request):
  360. """
  361. create the request context for all controllers
  362. """
  363. linotp_config = getLinotpConfig()
  364. request_context['Config'] = linotp_config
  365. request_context['Policies'] = getPolicies()
  366. request_context['translate'] = translate
  367. initResolvers()
  368. request_params = {}
  369. try:
  370. request_params.update(request.params)
  371. except UnicodeDecodeError as exx:
  372. log.error("Faild to decode request parameters %r" % exx)
  373. request_context['Params'] = request_params
  374. authUser = None
  375. try:
  376. authUser = getUserFromRequest(request)
  377. except UnicodeDecodeError as exx:
  378. log.error("Faild to decode request parameters %r" % exx)
  379. request_context['AuthUser'] = authUser
  380. requestUser = None
  381. try:
  382. requestUser = getUserFromParam(request_params, True)
  383. except UnicodeDecodeError as exx:
  384. log.error("Faild to decode request parameters %r" % exx)
  385. request_context['RequestUser'] = requestUser
  386. client = None
  387. try:
  388. client = get_client(request=request)
  389. except UnicodeDecodeError as exx:
  390. log.error("Faild to decode request parameters %r" % exx)
  391. request_context['Client'] = client
  392. request_context['Audit'] = Audit
  393. request_context['audit'] = Audit.initialize(request,
  394. client=client)
  395. defaultRealm = ""
  396. try:
  397. defaultRealm = getDefaultRealm(linotp_config)
  398. except UnicodeDecodeError as exx:
  399. log.error("Faild to decode request parameters %r" % exx)
  400. request_context['defaultRealm'] = defaultRealm
  401. realms = None
  402. try:
  403. realms = getRealms()
  404. except UnicodeDecodeError as exx:
  405. log.error("Faild to decode request parameters %r" % exx)
  406. request_context['Realms'] = realms
  407. request_context['hsm'] = None
  408. if hasattr(self, "hsm"):
  409. request_context['hsm'] = self.hsm
  410. # copy some system entries from pylons
  411. syskeys = {
  412. "radius.nas_identifier": "LinOTP",
  413. "radius.dictfile": "/etc/linotp2/dictionary"
  414. }
  415. sysconfig = {}
  416. for key, default in syskeys.items():
  417. try:
  418. sysconfig[key] = config.get(key, default)
  419. except:
  420. log.info('no sytem config entry %s' % key)
  421. request_context['SystemConfig'] = sysconfig
  422. # eof ########################################################################