def samlcheck(self): ''' This function is used to validate the username and the otp value/password in a SAML environment. If ``linotp.allowSamlAttributes = True`` then the attributes of the authenticated users are also contained in the response. method: validate/samlcheck arguments: * user: username / loginname * pass: the password that consists of a possible fixes password component and the OTP value * realm: optional realm to match the user to a useridresolver returns: JSON response ''' try: opt = None param = self.request_params (ok, opt) = self._check(param) attributes = {} if True == ok: allowSAML = False try: allowSAML = getFromConfig("allowSamlAttributes") except: log.warning("[samlcheck] Calling controller samlcheck. But allowSamlAttributes is False.") if "True" == allowSAML: ## Now we get the attributes of the user user = getUserFromParam(param) (uid, resId, resIdC) = getUserId(user) userInfo = getUserInfo(uid, resId, resIdC) log.debug("[samlcheck] getting attributes for: %s@%s" % (user.login, user.realm)) res = userInfo for key in ['username', 'surname', 'mobile', 'phone', 'givenname', 'email']: if key in res: attributes[key] = res[key] Session.commit() return sendResult(response, { 'auth': ok, 'attributes' : attributes } , 0, opt) except Exception as exx: log.exception("[samlcheck] validate/check failed: %r" % exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def check_yubikey(self): ''' This function is used to validate the output of a yubikey method: validate/check_yubikey :param pass: The password that consist of the static yubikey prefix and the otp :type pass: string :return: JSON Object returns: JSON response:: { "version": "LinOTP 2.4", "jsonrpc": "2.0", "result": { "status": true, "value": false }, "detail" : { "username": username, "realm": realm }, "id": 0 } ''' param = request.params passw = getParam(param, "pass", required) try: ok = False try: vh = ValidationHandler() ok, opt = vh.checkYubikeyPass(passw) c.audit['success'] = ok except AuthorizeException as exx: log.warning("[check_yubikey] authorization failed for validate/check_yubikey: %r" % exx) c.audit['success'] = False c.audit['info'] = unicode(exx) ok = False Session.commit() return sendResult(response, ok, 0, opt=opt) except Exception as exx: log.exception("[check_yubikey] validate/check_yubikey failed: %r" % exx) c.audit['info'] = unicode(exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close() log.debug('[check_yubikey] done')
def pair(self): try: params = dict(**request.params) enc_response = params.get('pairing_response') if enc_response is None: raise Exception('Parameter missing') dec_response = decrypt_pairing_response(enc_response) if not dec_response.serial: raise ValidateError('Pairing responses with no serial attached ' 'are currently not implemented.') serial = dec_response.serial user_public_key = dec_response.user_public_key user_token_id = dec_response.user_token_id user = dec_response.user_login user = getUserFromParam(params, optional) # TODO: pairing policy tokens = getTokens4UserOrSerial(None, serial) if not tokens: raise Exception('Invalid serial in pairing response') if len(tokens) > 1: raise Exception('Multiple tokens found. Pairing not possible') token = tokens[0] if token.type != 'qr': raise Exception('Pairing is only implemented for the qrtoken') token.ensure_state('pairing_url_sent') token.addToTokenInfo('user_token_id', user_token_id) b64_user_public_key = b64encode(user_public_key) token.addToTokenInfo('user_public_key', b64_user_public_key) params['serial'] = serial params['user_public_key'] = user_public_key params['user_token_id'] = user_token_id params['user'] = user params['content_type'] = CONTENT_TYPE_PAIRING params['data'] = serial token.change_state('pairing_response_received') Session.commit() return sendResult(response, False) except Exception: Session.rollback() return sendResult(response, False, 0, status=False) finally: Session.close()
def check_t(self): param = self.request_params.copy() value = {} ok = False opt = {} try: if 'pass' not in param: raise ParameterError("Missing parameter: 'pass'") passw = param['pass'] transid = param.get('state', None) if transid is not None: param['transactionid'] = transid del param['state'] if transid is None: transid = param.get('transactionid', None) if transid is None: raise Exception("missing parameter: state or transactionid!") vh = ValidationHandler() (ok, opt) = vh.check_by_transactionid(transid=transid, passw=passw, options=param) value['value'] = ok value['failcount'] = int(opt.get('failcount', 0)) c.audit['success'] = ok Session.commit() qr = param.get('qr', None) if qr and opt and 'message' in opt: try: dataobj = opt.get('message') param['alt'] = "%s" % opt if 'transactionid' in opt: param['transactionid'] = opt['transactionid'] return sendQRImageResult(response, dataobj, param) except Exception as exc: log.warning("failed to send QRImage: %r " % exc) return sendQRImageResult(response, opt, param) else: return sendResult(response, value, 1, opt=opt) except Exception as exx: log.exception("[check_t] validate/check_t failed: %r" % exx) c.audit['info'] = unicode(exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def check_status(self): """ check the status of a transaction - for polling support """ try: param = self.request_params # # we require either state or transactionid as parameter transid = param.get('state', param.get('transactionid', None)) if not transid: raise ParameterError(_('Missing required parameter "state" or ' '"transactionid"!')) # # serial is an optional parameter serial = param.get('serial', None) # user is an optional parameter: # if no 'user' in the parameters, the User object will be empty user = getUserFromParam(param) passw = param.get('pass') if passw is None: raise ParameterError(_('Missing required parameter "pass"!')) use_offline = param.get('use_offline', False) va = ValidationHandler() ok, opt = va.check_status(transid=transid, user=user, serial=serial, password=passw, use_offline=use_offline) c.audit['success'] = ok c.audit['info'] = unicode(opt) Session.commit() return sendResult(response, ok, 0, opt=opt) except Exception as exx: log.exception("check_status failed: %r" % exx) c.audit['info'] = unicode(exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def check_status(self): """ check the status of a transaction - for polling support """ param = {} ok = False opt = None try: param.update(request.params) transid = param.get('state', param.get('transactionid', None)) if not transid: raise ParameterError(_('Missing required parameter "state" or ' '"transactionid"!')) serial = param.get('serial', None) user = getUserFromParam(param, False) if not user and not serial: raise ParameterError(_('Missing required parameter "serial"' ' or "user"!')) passw = param.get('pass', None) if not passw: raise ParameterError(_('Missing required parameter "pass"!')) use_offline = param.get('use_offline', False) va = ValidationHandler() ok, opt = va.check_status(transid=transid, user=user, serial=serial, password=passw, use_offline=use_offline) c.audit['success'] = ok c.audit['info'] = unicode(opt) Session.commit() return sendResult(response, ok, 0, opt=opt) except Exception as exx: log.exception("check_status failed: %r" % exx) c.audit['info'] = unicode(exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close() log.debug('[check] done')
def license(self): """ license return the support status, which is community support by default or the support subscription info, which could be the old license :return: json result with license info """ res = {} try: try: license_info, license_sig = getSupportLicenseInfo() except InvalidLicenseException as err: if err.type != 'UNLICENSED': raise err opt = {'valid': False, 'message': "%r" % err } return sendResult(response, {}, 1, opt=opt) # Add Extra info # if needed; use details = None ... for no details!)... license_ok, license_msg = verifyLicenseInfo(license_info, license_sig) if not license_ok: details = {'valid': license_ok, 'message': license_msg } else: details = {'valid': license_ok} res['token-num'] = int(license_info.get('token-num', 0)) # get all active tokens from all realms (including norealm) monit_handler = MonitorHandler() active_tokencount = monit_handler.get_active_tokencount() res['token-active'] = active_tokencount res['token-left'] = res['token-num'] - active_tokencount return sendResult(response, res, 1, opt=details) except Exception as exception: log.exception(exception) return sendError(response, exception) finally: Session.close() log.debug('[license] done')
def delete_all(self): """ method: reporting/delete_all description: delete the reporting database table returns: dict in which value is the number of deleted rows exception: if an error occurs an exception is serialized and returned """ try: result = delete_reporting() Session.commit() return sendResult(response, result) except PolicyException as policy_exception: log.exception(policy_exception) Session.rollback() return sendError(response, unicode(policy_exception), 1) except Exception as exc: log.exception(exc) Session.rollback() return sendError(response, exc) finally: Session.close() log.debug('[delete_all] done')
def activeUsers(self): """ method: monitoring/activeUsers description: for each realm, display the resolvers and the number of users which have at least one assigned active token per resolver the 'total' gives the number of all users, which are in an allowed realm and own an active token users are conted per resolver (not per realm), so if resolver is in multiple realms and one user ons tokens in 2 realms, the user will be counted only once arguments: * realms - optional: takes realms, only information on these realms will be displayed """ result = {} try: param = request.params request_realms = param.get('realms', '').split(',') monit_handl = MonitorHandler() policies = getAdminPolicies('activeUsers', scope='monitoring') realm_whitelist = [] if policies['active'] and policies['realms']: realm_whitelist = policies.get('realms') # if there are no policies for us, we are allowed to see all realms if not realm_whitelist or '*' in realm_whitelist: realm_whitelist = request_context['Realms'].keys() realms = match_realms(request_realms, realm_whitelist) realm_info = {} for a_realm in realms: realm_info[a_realm] = monit_handl.active_users_per_realm(a_realm) result['Realms'] = realm_info result['total'] = monit_handl.active_users_total(realms) return sendResult(response, result) except PolicyException as policy_exception: log.exception(policy_exception) Session.rollback() return sendError(response, unicode(policy_exception), 1) except Exception as exc: log.exception(exc) Session.rollback() return sendError(response, exc) finally: Session.close() log.debug('[resolvers] done')
def webkdc_userinfo(self): # Called by WebAuth via the Elm remctld scripts. # Returns information about whether the user owns any tokens. # TODO: Require some sort of session token. param = {} try: param.update(request.params) user = getUserFromParam(param, optionalOrRequired = True) if (user is not None and user.isEmpty() == False): (userid, idResolver, idResolverClass) = getUserId(user) sqlQuery = Session.query(model.Token).with_lockmode("update").filter( model.Token.LinOtpUserid == userid).filter( model.Token.LinOtpIdResClass == idResolverClass).filter( model.Token.LinOtpIsactive == 1) tokenList = [] for token in sqlQuery: tokenList.append(token.LinOtpTokenSerialnumber) Session.commit() return sendResult(response, tokenList, 0) except Exception as exx: log.error("[webkdc_userinfo] validate/webkdc_userinfo failed: %r" % exx) log.error("[webkdc_userinfo] %s" % traceback.format_exc()) Session.rollback() return sendError(response, u"validate/webkdc_userinfo failed: %s" % unicode(exx), 0) finally: Session.close()
def migrate_resolver(self): from linotp.lib.tools.migrate_resolver import MigrateResolverHandler ret = {} try: src = self.request_params['from'] target = self.request_params['to'] from linotp.lib.resolver import getResolverList resolvers = getResolverList() src_resolver = resolvers.get(src, None) target_resolver = resolvers.get(target, None) if not target_resolver or not src_resolver: raise Exception('Src or Target resolver is undefined!') mg = MigrateResolverHandler() ret = mg.migrate_resolver(src=src_resolver, target=target_resolver) Session.commit() return sendResult(response, ret) except Exception as e: log.exception("failed: %r" % e) Session.rollback() return sendError(response, e, 1) finally: Session.close()
def storageEncryption(self): """ check if hsm/enckey encrypts value before storing it to config db :return: true if a new value gets encryptet before beeing stored in db """ try: if hasattr(c, 'hsm') == False or isinstance(c.hsm, dict) == False: raise HSMException('no hsm defined in execution context!') hsm = c.hsm.get('obj') if hsm is None or hsm.isReady() == False: raise HSMException('hsm not ready!') hsm_class = str(type(hsm)) enc_type = hsm_class.split('.')[-1] enc_type = enc_type.strip("'>") enc_name = hsm.name res = {'cryptmodul_type': enc_type, 'cryptmodul_name': enc_name} monit_handler = MonitorHandler() res['encryption'] = monit_handler.check_encryption() return sendResult(response, res, 1) except Exception as exception: log.exception(exception) return sendError(response, exception) finally: Session.close() log.debug('[encryption] done')
def _flexi_error(self, error): res = { "page": 1, "total": 1, "rows": [ { 'id' : 'error', 'cell' : ['E r r o r', error, '', '', '', '', '', '' ] } ] } return sendResult(response,res)
def delete_all(self): """ method: reporting/delete_all description: delete the reporting database table returns: dict in which value is the number of deleted rows exception: if an error occurs an exception is serialized and returned """ try: param = request.params request_realms = param.get('realms', '').split(',') status = param.get('status', ['total']) if status != ['total']: status = status.split(',') realm_whitelist = [] policies = getAdminPolicies('tokens', scope='monitoring') if policies['active'] and policies['realms']: realm_whitelist = policies.get('realms') # if there are no policies for us, we are allowed to see all realms if not realm_whitelist or '*' in realm_whitelist: realm_whitelist = request_context['Realms'].keys() realms = match_realms(request_realms, realm_whitelist) if '*' in status: status.remove('*') status.extend(['active', 'inactive', 'assigned', 'unassigned', 'active&assigned', 'active&unassigned', 'inactive&assigned', 'inactive&unassigned', 'total']) result = delete(realms=realms, status=status) Session.commit() return sendResult(response, result) except PolicyException as policy_exception: log.exception(policy_exception) Session.rollback() return sendError(response, unicode(policy_exception), 1) except Exception as exc: log.exception(exc) Session.rollback() return sendError(response, exc) finally: Session.close() log.debug('[delete_all] done')
def samlcheck(self): """ This function is used to validate the username and the otp value/password in a SAML environment. If ``linotp.allowSamlAttributes = True`` then the attributes of the authenticated users are also contained in the response. method: validate/samlcheck arguments: * user: username / loginname * pass: the password that consists of a possible fixes password component and the OTP value * realm: optional realm to match the user to a useridresolver returns: JSON response """ try: opt = None param = request.params (ok, opt) = self._check(param) attributes = {} if True == ok: allowSAML = False try: allowSAML = getFromConfig("allowSamlAttributes") except: log.warning("[samlcheck] Calling controller samlcheck. But allowSamlAttributes == False.") if "True" == allowSAML: ## Now we get the attributes of the user user = getUserFromParam(param, optional) (uid, resId, resIdC) = getUserId(user) userInfo = getUserInfo(uid, resId, resIdC) # users = getUserList({ 'username':user.getUser()} , user) log.debug("[samlcheck] getting attributes for: %s@%s" % (user.getUser(), user.getRealm())) res = userInfo for key in ["username", "surname", "mobile", "phone", "givenname", "email"]: if key in res: attributes[key] = res[key] Session.commit() return sendResult(response, {"auth": ok, "attributes": attributes}, 0, opt) except Exception as exx: log.error("[samlcheck] validate/check failed: %r" % exx) log.error("[samlcheck] %s" % traceback.format_exc()) Session.rollback() return sendError(response, "validate/samlcheck failed: %s" % unicode(exx), 0) finally: Session.close() log.debug("[samlcheck] done")
def getActivationCode(self): ''' method: ocra/getActivationCode description: returns an valid example activcation code arguments: ./. returns: JSON with "activationcode": "JZXW4ZI=2A" ''' from linotp.lib.crypt import createActivationCode res = {} #description = 'ocra/getActivationCode' try: params = getLowerParams(request.params) log.debug("[getActivationCode]: %r" % params) checkPolicyPre('ocra', "activationcode") ac = str(params.get('activationcode')) activationCode = createActivationCode(acode=ac) res = {'activationcode':activationCode} Session.commit() return sendResult(response, res, 1) except PolicyException as pe: log.error("[getActivationCode] policy failed: %r" % pe) log.error("[getActivationCode] %s" % traceback.format_exc()) Session.rollback() return sendError(response, unicode(pe)) except Exception as exx: log.error("[getActivationCode] failed: %r" % exx) log.error("[getActivationCode] %s" % traceback.format_exc()) Session.rollback() return sendError(response, unicode(exx), 0) finally: Session.close() log.debug('[getActivationCode] done')
def webkdc_validate(self): # Called by WebAuth via the Elm remctld scripts. # Verifies a one-time passcode and indicates how long # the token should be considered valid. param = {} try: param.update(request.params) username = param["user"] code = param["code"] user = User(username, "", "") th = TokenHandler() if ('token' in param): serial = param["token"] (ok, opt) = th.checkSerialPass(serial, code, options = None, user=user) else: (ok, opt) = th.checkUserPass(user, code) ret = { "success" : ok, } if (ok): ret['expiration'] = round(time.time()) + 60 * 60, # one hour from now else: if opt == None: opt = {} ret['error'] = c.audit.get('info') log.error("[webkdc_validate] authorization failed: %s" % ret['error']) ret['code'] = -310 Session.commit() return sendResult(response, ret, 0, opt=opt) except Exception as exx: log.error("[webkdc_validate] validate/webkdc_validate failed: %r" % exx) log.error("[webkdc_validate] %s" % traceback.format_exc()) Session.rollback() return sendError(response, u"validate/webkdc_validate failed: %s" % unicode(exx), 0) finally: Session.close()
def setPassword(self): """ abilty to set password in managed / admin_user resolver """ try: old_pw = self.request_params['old_password'] new_pw = self.request_params['new_password'] username = request_context['AuthUser'].get('login', '') if not username: raise Exception("Missing authenticated user!") sql_url = linotp.model.meta.engine.url # -------------------------------------------------------------- -- # the set password handling: # any error will raise an excecption which will be displayed # to the user c.audit['administrator'] = username c.audit['info'] = 'setPassword' set_pw_handler = SetPasswordHandler(DataBaseContext(sql_url)) set_pw_handler.set_password(username, old_password=old_pw, new_password=new_pw) c.audit['success'] = True return sendResult(response, obj=True, opt={'detail': ('password updated for %r' % username) }) except Exception as exx: c.audit['success'] = False log.exception(exx) Session.rollback() return sendError(response, exx) finally: Session.close()
def getActivationCode(self): """ method: ocra/getActivationCode description: returns an valid example activcation code arguments: ./. returns: JSON with "activationcode": "JZXW4ZI=2A" """ from linotp.lib.crypt import createActivationCode res = {} # description = 'ocra/getActivationCode' try: params = getLowerParams(request.params) log.debug("[getActivationCode]: %r" % params) checkPolicyPre("ocra", "activationcode", context=self.request_context) ac = str(params.get("activationcode")) activationCode = createActivationCode(acode=ac) res = {"activationcode": activationCode} Session.commit() return sendResult(response, res, 1) except PolicyException as pe: log.exception("[getActivationCode] policy failed: %r" % pe) Session.rollback() return sendError(response, unicode(pe)) except Exception as exx: log.exception("[getActivationCode] failed: %r" % exx) Session.rollback() return sendError(response, unicode(exx), 0) finally: Session.close() log.debug("[getActivationCode] done")
def encryption(self): """ check if hsm encrypts value before storing it to config db :return: """ try: monit_handler = MonitorHandler(context=self.request_context) res = {'encryption': monit_handler.check_encryption()} return sendResult(response, res, 1) except Exception as exception: log.exception(exception) return sendError(response, exception) finally: Session.close() log.debug('[__after__] done')
def setLogLevel(self): """ set the log level of a certain logger which is identified by the url parameter loggerName. example call: POST /maintenance/setLogLevel loggerName=linotp.lib.user level=10 (sets the log level of the user library to DEBUG) if loggerName is omitted, the root logger is assumed. """ try: # if no logger name is supplied we default to '' (which translates # to the root logger in the python stdlib logging api) name = self.request_params.get('loggerName', '') # ---------------------------------------------------------------- try: level = self.request_params.get('level', 0) level = int(level) except ValueError as e: raise Exception("debug level {} contains nondigits!".format(level)) # ---------------------------------------------------------------------- set_logging_level(name, level) Session.commit() return sendResult(response, True) except Exception as exx: Session.rollback() log.exception(exx) return sendError(response, exx, 1) finally: Session.close()
def delete_before(self): """ method: reporting/delete_before description: delete all entries from reporting database which are older than date date must be given in format: 'yyyy-mm-dd' returns: dict in which value is the number of deleted rows exception: if an error occurs an exception is serialized and returned """ try: param = request.params border_day = param.get('date') # this may throw ValueError if date is in wrong format datetime.strptime(border_day, "%Y-%m-%d") result = delete_before(border_day) Session.commit() return sendResult(response, result) except PolicyException as policy_exception: log.exception(policy_exception) Session.rollback() return sendError(response, unicode(policy_exception), 1) except ValueError as value_error: log.exception(value_error) Session.rollback() return sendError(response, unicode(value_error), 1) except Exception as exc: log.exception(exc) Session.rollback() return sendError(response, exc) finally: Session.close() log.debug('[tokens] done')
def config(self): """ check if Config- Database exists touches DB and checks if date of last read is new :return: a json result with: { "head": [], "value": {"sync": "True"} } exception: if an error occurs an exception is serialized and returned """ result = {} try: monit_handler = MonitorHandler() result = monit_handler.get_sync_status() # useful counts: counts = monit_handler.get_config_info() result.update(counts) ldap = 13 * result['ldapresolver'] sql = 12 * result['sqlresolver'] policies = 7 * result['policies'] realms = result['realms'] passwd = result['passwdresolver'] total = result['total'] result['netto'] = total - ldap - sql - passwd - policies - realms return sendResult(response, result) except Exception as exception: log.exception(exception) return sendError(response, exception) finally: Session.close() log.debug('[config] done')
def check_url(self): ''' This function works with pam_url. ''' ok = False param = {} try: param.update(request.params) try: (ok, opt) = self._check(param) except AuthorizeException as acc: log.warning( "[check_url] authorization failed for validate/check_url: %r" % acc) c.audit['success'] = False c.audit['action_detail'] = unicode(acc) ok = False Session.commit() response.headers['blablafoo'] = 'application/json' ## TODO: this code seems not to be finished if not ok: abort(403) else: return "Preshared Key Todo" except webob.exc.HTTPUnauthorized as acc: ## the exception, when an abort() is called if forwarded log.exception("[__before__::%r] webob.exception %r" % acc) Session.rollback() raise acc except Exception as exx: log.exception("[check_url] validate/check_url failed: %r" % exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def logout(self): """Logs a user out by obliterating their JWT access token cookies. NOTE: We may wish to block further use of the access token in question in case the user has saved a copy somewhere. See the Flask-JWT-Extended docs for ideas about how to do this. """ auth_user = getUserFromRequest() response = sendResult( None, True, opt={"message": f"Logout successful for {auth_user}"}) unset_jwt_cookies(response) # jti: jwt unique identifier raw_jwt = get_raw_jwt() jti = raw_jwt["jti"] expires_at = get_raw_jwt()["exp"] expires_in = int(expires_at - datetime.now().timestamp()) current_app.jwt_blocklist.add_item(jti, expiry=expires_in) return response
def autosms(self): ''' This function is used to test the autosms policy method: testing/autosms arguments: user - username / loginname realm - additional realm to match the user to a useridresolver returns: JSON response ''' param = request.params try: if isSelfTest() is False: Session.rollback() return sendError( response, "The testing controller can only" " be used in SelfTest mode!", 0) if "user" not in param: raise ParameterError("Missing parameter: 'user'") ok = get_auth_AutoSMSPolicy() Session.commit() return sendResult(response, ok, 0) except Exception as exx: log.exception("[autosms] validate/check failed: %r", exx) Session.rollback() return sendError(response, ("validate/check failed: %r", exx), 0) finally: Session.close()
def check_url(self): ''' This function works with pam_url. ''' ok = False param = {} try: param.update(request.params) try: (ok, opt) = self._check(param) except AuthorizeException as acc: log.warning("[check_url] authorization failed for validate/check_url: %r" % acc) c.audit['success'] = False c.audit['action_detail'] = unicode(acc) ok = False Session.commit() response.headers['blablafoo'] = 'application/json' ## TODO: this code seems not to be finished if not ok: abort(403) else: return "Preshared Key Todo" except webob.exc.HTTPUnauthorized as acc: ## the exception, when an abort() is called if forwarded log.exception("[__before__::%r] webob.exception %r" % acc) Session.rollback() raise acc except Exception as exx: log.exception("[check_url] validate/check_url failed: %r" % exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close() log.debug("[check_url] done")
def check_status(self): """ simple check if LinOTP backend services are up and running - support for checking that the Config database could be accessed """ try: opt = {} config_count = Session.query(Config).count() opt['config'] = {'entries': config_count} return sendResult(response, True, 0, opt=opt) except Exception as exx: Session.rollback() log.exception(exx) abort(500, "%r" % exx.message) finally: Session.close()
def config(self): """ check if Config- Database exists touches DB and checks if date of last read is new :return: a json result with: { "head": [], "value": {"sync": "True"} } exception: if an error occurs an exception is serialized and returned """ result = {} try: monit_handler = MonitorHandler() result = monit_handler.get_sync_status() # useful counts: counts = monit_handler.get_config_info() result.update(counts) ldap = 13 * result["ldapresolver"] sql = 12 * result["sqlresolver"] policies = 7 * result["policies"] realms = result["realms"] passwd = result["passwdresolver"] total = result["total"] result["netto"] = total - ldap - sql - passwd - policies - realms return sendResult(response, result) except Exception as exception: log.error(exception) return sendError(response, exception)
def webkdc_userinfo(self): # Called by WebAuth via the Elm remctld scripts. # Returns information about whether the user owns any tokens. # TODO: Require some sort of session token. param = {} try: param.update(request.params) user = getUserFromParam(param, optionalOrRequired=True) if (user is not None and user.isEmpty() == False): (userid, idResolver, idResolverClass) = getUserId(user) sqlQuery = Session.query( model.Token).with_lockmode("update").filter( model.Token.LinOtpUserid == userid).filter( model.Token.LinOtpIdResClass == idResolverClass ).filter(model.Token.LinOtpIsactive == True) tokenList = {} for token in sqlQuery: tokenList[token.LinOtpTokenSerialnumber] = json.loads( token.LinOtpTokenInfo) Session.commit() return sendResult(response, tokenList, 0) except Exception as exx: log.error("[webkdc_userinfo] validate/webkdc_userinfo failed: %r" % exx) log.error("[webkdc_userinfo] %s" % traceback.format_exc()) Session.rollback() return sendError( response, u"validate/webkdc_userinfo failed: %s" % unicode(exx), 0) finally: Session.close()
def autosms(self): ''' This function is used to test the autosms policy method: testing/autosms arguments: user - username / loginname realm - additional realm to match the user to a useridresolver returns: JSON response ''' log.debug('[autosms]') param = request.params try: if isSelfTest() == False: Session.rollback() return sendError(response, "The testing controller can only be used in SelfTest mode!", 0) user = getUserFromParam(param, required) ok = get_auth_AutoSMSPolicy() Session.commit() return sendResult(response, ok, 0) except Exception as e: log.error("[autosms] validate/check failed: %r", e) log.error("[autosms] %s" % traceback.format_exc()) Session.rollback() return sendError(response, "validate/check failed:" + unicode(e), 0) finally: Session.close() log.debug('[autosms] done')
def migrate_resolver(self): from linotp.lib.tools.migrate_resolver import MigrateResolverHandler params = {} ret = {} try: params.update(request.params) src = params['from'] target = params['to'] from linotp.lib.resolver import getResolverList resolvers = getResolverList() src_resolver = resolvers.get(src, None) target_resolver = resolvers.get(target, None) if not target_resolver or not src_resolver: raise Exception('Src or Target resolver is undefined!') mg = MigrateResolverHandler(context=self.request_context) ret = mg.migrate_resolver(src=src_resolver, target=target_resolver) Session.commit() return sendResult(response, ret) except Exception as e: log.exception("failed: %r" % e) Session.rollback() log.error('error getting token owner') return sendError(response, e, 1) finally: Session.close() log.debug('[enable] done')
def autosms(self): ''' This function is used to test the autosms policy method: testing/autosms arguments: user - username / loginname realm - additional realm to match the user to a useridresolver returns: JSON response ''' log.debug('[autosms]') param = request.params try: if isSelfTest() == False: Session.rollback() return sendError(response, "The testing controller can only be used in SelfTest mode!", 0) user = getUserFromParam(param, required) ok = get_auth_AutoSMSPolicy(context=self.request_context) Session.commit() return sendResult(response, ok, 0) except Exception as e: log.exception("[autosms] validate/check failed: %r", e) Session.rollback() return sendError(response, "validate/check failed:" + unicode(e), 0) finally: Session.close() log.debug('[autosms] done')
def migrate_resolver(self): from linotp.lib.tools.migrate_resolver import MigrateResolverHandler params = {} ret = {} try: params.update(request.params) src = params['from'] target = params['to'] from linotp.lib.resolver import getResolverList resolvers = getResolverList() src_resolver = resolvers.get(src, None) target_resolver = resolvers.get(target, None) if not target_resolver or not src_resolver: raise Exception('Src or Target resolver is undefined!') mg = MigrateResolverHandler() ret = mg.migrate_resolver(src=src_resolver, target=target_resolver) Session.commit() return sendResult(response, ret) except Exception as e: log.exception("failed: %r" % e) Session.rollback() log.error('error getting token owner') return sendError(response, e, 1) finally: Session.close() log.debug('[enable] done')
def check_url(self): """ This function works with pam_url. """ ok = False param = self.request_params try: try: (ok, opt) = self._check(param) except AuthorizeException as acc: log.warning( "[check_url] authorization failed for validate/check_url: %r", acc, ) g.audit["success"] = False g.audit["action_detail"] = str(acc) ok = False db.session.commit() response.headers["blablafoo"] = "application/json" # TODO: this code seems not to be finished if not ok: abort(403) else: return "Preshared Key Todo" except flap.HTTPUnauthorized as acc: # the exception, when an abort() is called if forwarded log.error("[__before__::%r] webob.exception %r", acc) db.session.rollback() raise acc except Exception as exx: log.error("[check_url] validate/check_url failed: %r", exx) db.session.rollback() return sendResult(response, False, 0)
def autosms(self): ''' This function is used to test the autosms policy method: testing/autosms arguments: user - username / loginname realm - additional realm to match the user to a useridresolver returns: JSON response ''' try: if isSelfTest() is False: Session.rollback() return sendError(response, "The testing controller can only" " be used in SelfTest mode!", 0) if "user" not in self.request_params: raise ParameterError("Missing parameter: 'user'") ok = get_auth_AutoSMSPolicy() Session.commit() return sendResult(response, ok, 0) except Exception as exx: log.exception("[autosms] validate/check failed: %r", exx) Session.rollback() return sendError(response, ("validate/check failed: %r", exx), 0) finally: Session.close()
def getsession(self): ''' This generates a session key and sets it as a cookie set_cookie is defined in python-webob:: def set_cookie(self, key, value='', max_age=None, path='/', domain=None, secure=None, httponly=False, version=None, comment=None, expires=None, overwrite=False): ''' import binascii try: web_host = request.environ.get('HTTP_HOST') # HTTP_HOST also contains the port number. We need to stript this! web_host = web_host.split(':')[0] log.debug("[getsession] environment: %s" % request.environ) log.debug("[getsession] found this web_host: %s" % web_host) random_key = os.urandom(SESSION_KEY_LENGTH) cookie = binascii.hexlify(random_key) log.debug( "[getsession] adding session cookie %s to response." % cookie) # we send all three to cope with IE8 response.set_cookie('helpdesk_session', value=cookie, domain=web_host) # this produces an error with the gtk client # response.set_cookie('admin_session', value=cookie, domain=".%" % web_host ) response.set_cookie('helpdesk_session', value=cookie, domain="") return sendResult(response, True) except Exception as e: log.exception( "[getsession] unable to create a session cookie: %r" % e) Session.rollback() return sendError(response, e) finally: Session.close()
def check_yubikey(self): ''' This function is used to validate the output of a yubikey method: validate/check_yubikey :param pass: The password that consist of the static yubikey prefix and the otp :type pass: string :return: JSON Object returns: JSON response:: { "version": "LinOTP 2.4", "jsonrpc": "2.0", "result": { "status": true, "value": false }, "detail" : { "username": username, "realm": realm }, "id": 0 } ''' try: try: passw = self.request_params['pass'] except KeyError: raise ParameterError("Missing parameter: 'pass'") ok = False try: vh = ValidationHandler() ok, opt = vh.checkYubikeyPass(passw) c.audit['success'] = ok except AuthorizeException as exx: log.warning( "[check_yubikey] authorization failed for validate/check_yubikey: %r" % exx) c.audit['success'] = False c.audit['info'] = unicode(exx) ok = False Session.commit() return sendResult(response, ok, 0, opt=opt) except Exception as exx: log.exception("[check_yubikey] validate/check_yubikey failed: %r" % exx) c.audit['info'] = unicode(exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def check(self): ''' This function is used to validate the username and the otp value/password. method: validate/check arguments: * user: The username or loginname * pass: The password that consist of a possible fixed password component and the OTP value * realm (optional): An optional realm to match the user to a useridresolver * challenge (optional): optional challenge + otp verification for challenge response token. This indicates, that tis request is a challenge request. * data (optional): optional challenge + otp verification for challenge response token. This indicates, that tis request is a challenge request. * state (optional): The optional id to respond to a previous challenge. * transactionid (optional): The optional id to respond to a previous challenge. returns: JSON response:: { "version": "LinOTP 2.4", "jsonrpc": "2.0", "result": { "status": true, "value": false }, "id": 0 } If ``status`` is ``true`` the request was handled successfully. If ``value`` is ``true`` the user was authenticated successfully. ''' param = self.request_params.copy() ok = False opt = None try: # prevent the detection if a user exist # by sending a request w.o. pass parameter try: (ok, opt) = self._check(param) except (AuthorizeException, ParameterError) as exx: log.warning( "[check] authorization failed for validate/check: %r" % exx) c.audit['success'] = False c.audit['info'] = unicode(exx) ok = False if is_auth_return(ok): if opt is None: opt = {} opt['error'] = c.audit.get('info') Session.commit() qr = param.get('qr', None) if qr and opt and 'message' in opt: try: dataobj = opt.get('message') param['alt'] = "%s" % opt if 'transactionid' in opt: param['transactionid'] = opt['transactionid'] return sendQRImageResult(response, dataobj, param) except Exception as exc: log.warning("failed to send QRImage: %r " % exc) return sendQRImageResult(response, opt, param) else: return sendResult(response, ok, 0, opt=opt) except Exception as exx: log.exception("[check] validate/check failed: %r" % exx) # If an internal error occurs or the SMS gateway did not send the SMS, we write this to the detail info. c.audit['info'] = "%r" % exx Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def userview_flexi(self): ''' This function is used to fill the flexigrid. Unlike the complex /admin/userlist function, it only returns a simple array of the tokens. ''' param = self.request_params try: c.page = param.get("page") c.filter = param.get("query") qtype = param.get("qtype") c.sort = param.get("sortname") c.dir = param.get("sortorder") c.psize = param.get("rp") c.realm = param.get("realm") user = getUserFromParam(param) # check admin authorization # check if we got a realm or resolver, that is ok! checkPolicyPre('admin', 'userlist', { 'user': user.login, 'realm': c.realm }) if c.filter == "": c.filter = "*" log.debug( "[userview_flexi] page: %s, filter: %s, sort: %s, dir: %s" % (c.page, c.filter, c.sort, c.dir)) if c.page is None: c.page = 1 if c.psize is None: c.psize = 20 c.userArray = getUserList({ qtype: c.filter, 'realm': c.realm }, user) c.userNum = len(c.userArray) lines = [] for u in c.userArray: # shorten the useridresolver, to get a better display value resolver_display = "" if "useridresolver" in u: if len(u['useridresolver'].split(".")) > 3: resolver_display = u['useridresolver'].split(".")[ 3] + " (" + u['useridresolver'].split(".")[1] + ")" else: resolver_display = u['useridresolver'] lines.append({ 'id': u['username'], 'cell': [ (u['username']) if 'username' in u else (""), (resolver_display), (u['surname']) if 'surname' in u else (""), (u['givenname']) if 'givenname' in u else (""), (u['email']) if 'email' in u else (""), (u['mobile']) if 'mobile' in u else (""), (u['phone']) if 'phone' in u else (""), (u['userid']) if 'userid' in u else (""), ] }) # sorting reverse = False sortnames = { 'username': 0, 'useridresolver': 1, 'surname': 2, 'givenname': 3, 'email': 4, 'mobile': 5, 'phone': 6, 'userid': 7 } if c.dir == "desc": reverse = True lines = sorted(lines, key=lambda user: user['cell'][sortnames[c.sort]], reverse=reverse) # end: sorting # reducing the page if c.page and c.psize: page = int(c.page) psize = int(c.psize) start = psize * (page - 1) end = start + psize lines = lines[start:end] # We need to return 'page', 'total', 'rows' res = {"page": int(c.page), "total": c.userNum, "rows": lines} c.audit['success'] = True Session.commit() return sendResult(response, res) except PolicyException as pe: log.exception( "[userview_flexi] Error during checking policies: %r" % pe) Session.rollback() return sendError(response, str(pe), 1) except Exception as e: log.exception("[userview_flexi] failed: %r" % e) Session.rollback() return sendError(response, e) finally: Session.close()
def tokenview_flexi(self): ''' This function is used to fill the flexigrid. Unlike the complex /admin/show function, it only returns a simple array of the tokens. ''' param = self.request_params try: c.page = param.get("page") c.filter = param.get("query") c.qtype = param.get("qtype") c.sort = param.get("sortname") c.dir = param.get("sortorder") c.psize = param.get("rp") filter_all = None filter_realm = None user = User() if c.qtype == "loginname": # we take by default the given expression as a loginname, # especially if it contains a "*" wildcard. # it only might be more, a user and a realm, if there # is an '@' sign in the loginname and the part after the # last '@' sign is matching an existing realm user = User(login=c.filter) if "*" not in c.filter and "@" in c.filter: login, _, realm = c.filter.rpartition("@") if realm.lower() in getRealms(): user = User(login, realm) if not user.exists(): user = User(login=c.filter) elif c.qtype == "all": filter_all = c.filter elif c.qtype == "realm": filter_realm = c.filter # check admin authorization res = checkPolicyPre('admin', 'show', param, user=user) filterRealm = res['realms'] # check if policies are active at all # If they are not active, we are allowed to SHOW any tokens. pol = getAdminPolicies("show") # If there are no admin policies, we are allowed to see all realms if not pol['active']: filterRealm = ["*"] # check if we only want to see ONE realm or see all realms we are allowerd to see. if filter_realm: if filter_realm in filterRealm or '*' in filterRealm: filterRealm = [filter_realm] log.debug( "[tokenview_flexi] admin >%s< may display the following realms: %s" % (pol['admin'], pol['realms'])) log.debug( "[tokenview_flexi] page: %s, filter: %s, sort: %s, dir: %s" % (c.page, c.filter, c.sort, c.dir)) if c.page is None: c.page = 1 if c.psize is None: c.psize = 20 log.debug( "[tokenview_flexi] calling TokenIterator for user=%s@%s, filter=%s, filterRealm=%s" % (user.login, user.realm, filter_all, filterRealm)) c.tokenArray = TokenIterator(user, None, c.page, c.psize, filter_all, c.sort, c.dir, filterRealm=filterRealm) c.resultset = c.tokenArray.getResultSetInfo() # If we have chosen a page to big! lines = [] for tok in c.tokenArray: uid = tok['LinOtp.Userid'] uid = uid.decode('utf-8') if isinstance(uid, bytes) else uid lines.append({ 'id': tok['LinOtp.TokenSerialnumber'], 'cell': [ tok['LinOtp.TokenSerialnumber'], tok['LinOtp.Isactive'], tok['User.username'], tok['LinOtp.RealmNames'], tok['LinOtp.TokenType'], tok['LinOtp.FailCount'], tok['LinOtp.TokenDesc'], tok['LinOtp.MaxFail'], tok['LinOtp.OtpLen'], tok['LinOtp.CountWindow'], tok['LinOtp.SyncWindow'], uid, tok['LinOtp.IdResClass'].split('.')[-1], ] }) # We need to return 'page', 'total', 'rows' res = { "page": int(c.page), "total": c.resultset['tokens'], "rows": lines } c.audit['success'] = True Session.commit() # The flexi handler should support std LinOTP output return sendResult(response, res) except PolicyException as pe: log.exception( "[tokenview_flexi] Error during checking policies: %r" % pe) Session.rollback() return sendError(response, str(pe), 1) except Exception as e: log.exception("[tokenview_flexi] failed: %r" % e) Session.rollback() return sendError(response, e) finally: Session.close()
def tokens(self): """ method: monitoring/tokens description: Displays the number of tokens (with status) per realm (one token might be in multiple realms). The Summary gives the sum of all tokens in all given realms and might be smaller than the summ of all tokens as tokens which have two realms are only counted once! arguments: * status - optional: takes assigned or unassigned, give the number of tokens with this characteristic * realms - optional: takes realms, only the number of tokens in these realms will be displayed returns: a json result with: { "head": [], "data": [ [row1], [row2] .. ] } exception: if an error occurs an exception is serialized and returned """ result = {} try: # extract and strip the list of requested statuses + default # statuses and ignore empty values. status_params = self.request_params.get("status", "").split(",") status = list( set( ["total", "total users"] + [s.strip() for s in status_params if s.strip()] ) ) request_realms = self.request_params.get("realms", "").split(",") monit_handler = MonitorHandler() realm_whitelist = [] policies = getAdminPolicies("tokens", scope="monitoring") if policies["active"] and policies["realms"]: realm_whitelist = policies.get("realms") # if there are no policies for us, we are allowed to see all realms if not realm_whitelist or "*" in realm_whitelist: realm_whitelist = list(request_context["Realms"].keys()) realms = match_realms(request_realms, realm_whitelist) realm_info = {} for a_realm in realms: token_count = monit_handler.token_count([a_realm], status) realm_info[a_realm] = token_count result["Summary"] = monit_handler.token_count(realms, status) result["Realms"] = realm_info db.session.commit() return sendResult(response, result) except PolicyException as policy_exception: log.error(policy_exception) db.session.rollback() return sendError(response, policy_exception, 1) except Exception as exc: log.error(exc) db.session.rollback() return sendError(response, exc)
def smspin(self): ''' This function is used in conjunction with an SMS token: the user authenticates with user and pin (pass) and will receive on his mobile an OTP as message method: validate/smspin arguments: * user: username / loginname * pass: the password that consists of a possible fixed password * realm: additional realm to match the user to a useridresolver returns: JSON response ''' ret = False param = self.request_params state = '' message = 'No sms message defined!' try: user = getUserFromParam(param) c.audit['user'] = user.login c.audit['realm'] = user.realm or getDefaultRealm() c.audit['success'] = 0 (ret, opt) = self._check(param) ## here we build some backward compatibility if type(opt) is dict: state = opt.get('state', '') or '' message = opt.get('message', '') or 'No sms message defined!' # sucessfull submit if (message in ['sms with otp already submitted', 'sms submitted'] and len(state) > 0): ret = True c.audit['success'] = 1 # sending sms failed should be an error elif message in ['sending sms failed']: ret = True c.audit['success'] = 0 # anything else is an exception else: raise Exception(message) Session.commit() return sendResult(response, ret, opt) except Exception as exx: log.exception("[smspin] validate/smspin failed: %r" % exx) # If an internal error occurs or the SMS gateway did not send # the SMS, we write this to the detail info. c.audit['info'] = unicode(exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def ok(self): return sendResult(response, True, 0)
def getotp(self): ''' This function is used to retrieve the current otp value for a given user or a given serial. If the user has more than one token, the list of the tokens is returend. method: gettoken/getotp arguments: user - username / loginname realm - additional realm to match the user to a useridresolver serial - the serial number of the token curTime - used ONLY for internal testing: datetime.datetime object returns: JSON response ''' getotp_active = config.get("GETOTP_ENABLED") if not getotp_active: return sendError(response, "getotp is not activated.", 0) param = self.request_params ret = {} res = -1 otpval = "" passw = "" serials = [] try: serial = getParam(param, "serial", optional) user = getUserFromParam(param) curTime = getParam(param, "curTime", optional) g.audit['user'] = user.login if "" != user.login: g.audit['realm'] = user.realm or getDefaultRealm() if serial: log.debug("[getotp] retrieving OTP value for token %s", serial) elif user.login: log.debug( "[getotp] retrieving OTP value for token for user " "%s@%s", user.login, user.realm) toks = getTokens4UserOrSerial(user, serial) tokennum = len(toks) if tokennum > 1: log.debug("[getotp] The user has more than one token." "Returning the list of serials") res = -3 for token in toks: serials.append(token.getSerial()) elif 1 == tokennum: serial = toks[0].getSerial() log.debug( "[getotp] retrieving OTP for token %s for user" " %s@%s", serial, user.login, user.realm) else: log.debug("[getotp] no token found for user %s@%s", user.login, user.realm) res = -4 else: res = -5 # if a serial was given or a unique serial could be # received from the given user. if serial: max_count = checkPolicyPre('gettoken', 'max_count', param) log.debug("[getmultiotp] max_count policy: %s", max_count) if max_count <= 0: return sendError( response, "The policy forbids receiving" " OTP values for the token %s in " "this realm" % serial, 1) (res, pin, otpval, passw) = getOtp(serial, curTime=curTime) g.audit['success'] = True if int(res) < 0: ret['result'] = False if -1 == otpval: ret['description'] = "No Token with this serial number" if -2 == otpval: ret['description'] = ("This Token does not support the" " getOtp function") if -3 == otpval: ret['description'] = "The user has more than one token" ret['serials'] = serials if -4 == otpval: ret['description'] = "No Token found for this user" if -5 == otpval: ret['description'] = ("you need to provide a user or " "a serial") else: ret['result'] = True ret['otpval'] = otpval ret['pin'] = pin ret['pass'] = passw db.session.commit() return sendResult(response, ret, 0) except PolicyException as pe: log.exception("[getotp] gettoken/getotp policy failed: %r", pe) db.session.rollback() return sendError(response, str(pe), 1) except Exception as exx: log.exception("[getotp] gettoken/getotp failed: %r", exx) db.session.rollback() return sendError(response, "gettoken/getotp failed: %s" % exx, 0)
def getmultiotp(self): ''' This function is used to retrieve multiple otp values for a given user or a given serial. If the user has more than one token, the list of the tokens is returend. method: gettoken/getmultiotp arguments: serial - the serial number of the token count - number of otp values to return curTime - used ONLY for internal testing: datetime.datetime object returns: JSON response ''' getotp_active = config.get("GETOTP_ENABLED") if not getotp_active: return sendError(response, "getotp is not activated.", 0) param = self.request_params ret = {} try: serial = getParam(param, "serial", required) count = int(getParam(param, "count", required)) curTime = getParam(param, "curTime", optional) view = getParam(param, "view", optional) r1 = checkPolicyPre('admin', 'getotp', param) log.debug("[getmultiotp] admin-getotp policy: %s", r1) max_count = checkPolicyPre('gettoken', 'max_count', param) log.debug("[getmultiotp] maxcount policy: %s", max_count) if count > max_count: count = max_count log.debug("[getmultiotp] retrieving OTP value for token %s", serial) ret = get_multi_otp(serial, count=int(count), curTime=curTime) ret["serial"] = serial g.audit['success'] = True db.session.commit() if view: c.ret = ret return render('/manage/multiotp_view.mako').decode('utf-8') else: return sendResult(response, ret, 0) except PolicyException as pe: log.exception("[getotp] gettoken/getotp policy failed: %r", pe) db.session.rollback() return sendError(response, str(pe), 1) except Exception as exx: log.exception("[getmultiotp] gettoken/getmultiotp failed: %r", exx) db.session.rollback() return sendError(response, "gettoken/getmultiotp failed: %r" % exx, 0)
def getmultiotp(self): ''' This function is used to retrieve multiple otp values for a given user or a given serial If the user has more than one token, the list of the tokens is returend. method: gettoken/getmultiotp arguments: serial - the serial number of the token count - number of otp values to return curTime - used ONLY for internal testing: datetime.datetime object returns: JSON response ''' getotp_active = config.get("linotpGetotp.active") if "True" != getotp_active: return sendError(response, "getotp is not activated.", 0) param = request.params ret = {} try: serial = getParam(param, "serial", required) count = int(getParam(param, "count", required)) curTime = getParam(param, "curTime", optional) view = getParam(param, "view", optional) r1 = checkPolicyPre('admin', 'getotp', param) log.debug("[getmultiotp] admin-getotp returned %s" % r1) max_count = checkPolicyPre('gettoken', 'max_count', param) log.debug("[getmultiotp] checkpolicypre returned %s" % max_count) if count > max_count: count = max_count log.debug("[getmultiotp] retrieving OTP value for token %s" % serial) ret = get_multi_otp(serial, count=int(count), curTime=curTime) ret["serial"] = serial c.audit['success'] = True Session.commit() if view: c.ret = ret return render('/manage/multiotp_view.mako') else: return sendResult(response, ret, 0) except PolicyException as pe: log.error("[getotp] gettoken/getotp policy failed: %r" % pe) log.error("[getotp] %s" % traceback.format_exc()) Session.rollback() return sendError(response, unicode(pe), 1) except Exception as e: log.error("[getmultiotp] gettoken/getmultiotp failed: %r" % e) log.error("[getmultiotp] %s" % traceback.format_exc()) Session.rollback() return sendError(response, "gettoken/getmultiotp failed: %s" % unicode(e), 0) finally: Session.close() log.debug("[getmultiotp] done")
def getotp(self): ''' This function is used to retrieve the current otp value for a given user or a given serial If the user has more than one token, the list of the tokens is returend. method: gettoken/getotp arguments: user - username / loginname realm - additional realm to match the user to a useridresolver serial - the serial number of the token curTime - used ONY for internal testing: datetime.datetime object returns: JSON response ''' getotp_active = config.get("linotpGetotp.active") if "True" != getotp_active: return sendError(response, "getotp is not activated.", 0) param = request.params ret = {} res = -1 otpval = "" passw = "" serials = [] try: serial = getParam(param, "serial", optional) user = getUserFromParam(param, optional) curTime = getParam(param, "curTime", optional) c.audit['user'] = user.login if "" != user.login: c.audit['realm'] = user.realm or getDefaultRealm() if serial: log.debug("[getotp] retrieving OTP value for token %s" % serial) elif user.login: log.debug( "[getotp] retrieving OTP value for token for user %s@%s" % (user.login, user.realm)) toks = getTokens4UserOrSerial(user, serial) tokennum = len(toks) if tokennum > 1: log.debug( "[getotp] The user has more than one token. Returning the list of serials" ) res = -3 for token in toks: serials.append(token.getSerial()) elif 1 == tokennum: serial = toks[0].getSerial() log.debug( "[getotp] retrieving OTP for token %s for user %s@%s" % (serial, user.login, user.realm)) else: log.debug("[getotp] no token found for user %s@%s" % (user.login, user.realm)) res = -4 else: res = -5 # if a serial was given or a unique serial could be received from the given user. if serial: max_count = checkPolicyPre('gettoken', 'max_count', param) log.debug("[getmultiotp] checkpolicypre returned %s" % max_count) if max_count <= 0: return sendError( response, "The policy forbids receiving OTP values for the token %s in this realm" % serial, 1) (res, pin, otpval, passw) = getOtp(serial, curTime=curTime) c.audit['success'] = True if int(res) < 0: ret['result'] = False if -1 == otpval: ret['description'] = "No Token with this serial number" if -2 == otpval: ret['description'] = "This Token does not support the getOtp function" if -3 == otpval: ret['description'] = "The user has more than one token" ret['serials'] = serials if -4 == otpval: ret['description'] = "No Token found for this user" if -5 == otpval: ret['description'] = "you need to provide a user or a serial" else: ret['result'] = True ret['otpval'] = otpval ret['pin'] = pin ret['pass'] = passw Session.commit() return sendResult(response, ret, 0) except PolicyException as pe: log.error("[getotp] gettoken/getotp policy failed: %r" % pe) log.error("[getotp] %s" % traceback.format_exc()) Session.rollback() return sendError(response, unicode(pe), 1) except Exception as e: log.error("[getotp] gettoken/getotp failed: %r" % e) log.error("[getotp] %s" % traceback.format_exc()) Session.rollback() return sendError(response, "gettoken/getotp failed: %s" % unicode(e), 0) finally: Session.close() log.debug('[getotp] done')
def userinfo(self): """ method: monitoring/userinfo description: for each realm, display the resolvers and the number of users per resolver arguments: * realms - optional: takes a realm, only information on this realm will be displayed returns: a json result with: { "head": [], "data": [ [row1], [row2] .. ] } """ result = {} try: param = request.params request_realms = param.get('realms', '').split(',') monit_handler = MonitorHandler() policies = getAdminPolicies('userinfo', scope='monitoring') realm_whitelist = [] if policies['active'] and policies['realms']: realm_whitelist = policies.get('realms') # if there are no policies for us, we are allowed to see all realms if not realm_whitelist or '*' in realm_whitelist: realm_whitelist = request_context['Realms'].keys() realms = match_realms(request_realms, realm_whitelist) if '/:no realm:/' in realms: realms.remove('/:no realm:/') realm_info = {} for a_realm in realms: realm_info[a_realm] = monit_handler.resolverinfo(a_realm) result['Realms'] = realm_info Session.commit() return sendResult(response, result) except PolicyException as policy_exception: log.exception(policy_exception) Session.rollback() return sendError(response, unicode(policy_exception), 1) except Exception as exc: log.exception(exc) Session.rollback() return sendError(response, exc) finally: Session.close()
def tokens(self): """ method: monitoring/tokens description: Displays the number of tokens (with status) per realm (one token might be in multiple realms). The Summary gives the sum of all tokens in all given realms and might be smaller than the summ of all tokens as tokens which have two realms are only counted once! arguments: * status - optional: takes assigned or unassigned, give the number of tokens with this characteristic * realms - optional: takes realms, only the number of tokens in these realms will be displayed returns: a json result with: { "head": [], "data": [ [row1], [row2] .. ] } exception: if an error occurs an exception is serialized and returned """ result = {} try: param = request.params status = param.get('status', ['total']) if status != ['total']: status = status.split(',') status.append('total') request_realms = param.get('realms', '').split(',') monit_handler = MonitorHandler() realm_whitelist = [] policies = getAdminPolicies('tokens', scope='monitoring') if policies['active'] and policies['realms']: realm_whitelist = policies.get('realms') # if there are no policies for us, we are allowed to see all realms if not realm_whitelist or '*' in realm_whitelist: realm_whitelist = request_context['Realms'].keys() realms = match_realms(request_realms, realm_whitelist) realm_info = {} for a_realm in realms: token_count = monit_handler.token_count([a_realm], status) realm_info[a_realm] = token_count result['Summary'] = monit_handler.token_count(realms, status) result['Realms'] = realm_info Session.commit() return sendResult(response, result) except PolicyException as policy_exception: log.exception(policy_exception) Session.rollback() return sendError(response, unicode(policy_exception), 1) except Exception as exc: log.exception(exc) Session.rollback() return sendError(response, exc) finally: Session.close()
def samlcheck(self): ''' This function is used to validate the username and the otp value/password in a SAML environment. If ``linotp.allowSamlAttributes = True`` then the attributes of the authenticated users are also contained in the response. method: validate/samlcheck arguments: * user: username / loginname * pass: the password that consists of a possible fixes password component and the OTP value * realm: optional realm to match the user to a useridresolver returns: JSON response ''' try: opt = None param = self.request_params (ok, opt) = self._check(param) attributes = {} if True == ok: allowSAML = False try: allowSAML = getFromConfig("allowSamlAttributes") except: log.warning( "[samlcheck] Calling controller samlcheck. But allowSamlAttributes is False." ) if "True" == allowSAML: ## Now we get the attributes of the user user = getUserFromParam(param) (uid, resId, resIdC) = getUserId(user) userInfo = getUserInfo(uid, resId, resIdC) log.debug("[samlcheck] getting attributes for: %s@%s" % (user.login, user.realm)) res = userInfo for key in [ 'username', 'surname', 'mobile', 'phone', 'givenname', 'email' ]: if key in res: attributes[key] = res[key] Session.commit() return sendResult(response, { 'auth': ok, 'attributes': attributes }, 0, opt) except Exception as exx: log.exception("[samlcheck] validate/check failed: %r" % exx) Session.rollback() return sendResult(response, False, 0) finally: Session.close()
def check_s(self): ''' This function is used to validate the serial and the otp value/password. method: validate/check_s arguments: * serial: the serial number of the token * pass: the password that consists of a possible fixes password component and the OTP value returns: JSON response ''' param = self.request_params options = {} options.update(param) for k in ['user', 'serial', "pass", "init"]: if k in options: del options[k] if 'init' in param: if isSelfTest() is True: options['initTime'] = param.get('init') try: passw = param.get("pass") serial = param.get('serial') if serial is None: user = param.get('user') if user is not None: user = getUserFromParam(param) toks = getTokens4UserOrSerial(user=user) if len(toks) == 0: raise Exception("No token found!") elif len(toks) > 1: raise Exception("More than one token found!") else: tok = toks[0].token desc = tok.get() realms = desc.get('LinOtp.RealmNames') if realms is None or len(realms) == 0: realm = getDefaultRealm() elif len(realms) > 0: realm = realms[0] userInfo = getUserInfo(tok.LinOtpUserid, tok.LinOtpIdResolver, tok.LinOtpIdResClass) user = User(login=userInfo.get('username'), realm=realm) serial = tok.getSerial() c.audit['serial'] = serial if isSelfTest() is True: initTime = param.get("init") if initTime is not None: if options is None: options = {} options['initTime'] = initTime options['scope'] = {"check_s": True} vh = ValidationHandler() (ok, opt) = vh.checkSerialPass(serial, passw, options=options) c.audit['success'] = ok Session.commit() qr = param.get('qr', None) if qr and opt and 'message' in opt: try: dataobj = opt.get('message') param['alt'] = "%s" % opt if 'transactionid' in opt: param['transactionid'] = opt['transactionid'] return sendQRImageResult(response, dataobj, param) except Exception as exc: log.warning("failed to send QRImage: %r " % exc) return sendQRImageResult(response, opt, param) else: return sendResult(response, ok, 0, opt=opt) except Exception as exx: log.exception("[check_s] validate/check_s failed: %r" % exx) c.audit['info'] = unicode(exx) Session.rollback() return sendResult(response, False, id=0, status=False) finally: Session.close()
def pair(self): """ validate/pair: for the enrollment of qr and push token """ try: # -------------------------------------------------------------- -- enc_response = self.request_params.get("pairing_response") if enc_response is None: raise Exception("Parameter missing") # -------------------------------------------------------------- -- dec_response = decrypt_pairing_response(enc_response) token_type = dec_response.token_type pairing_data = dec_response.pairing_data if ( not hasattr(pairing_data, "serial") or pairing_data.serial is None ): raise ValidateError( "Pairing responses with no serial attached" " are currently not implemented." ) # --------------------------------------------------------------- - # TODO: pairing policy tokens = getTokens4UserOrSerial(None, pairing_data.serial) if not tokens: raise Exception("Invalid serial in pairing response") if len(tokens) > 1: raise Exception("Multiple tokens found. Pairing not possible") token = tokens[0] # prepare some audit entries t_owner = token.getUser() realms = token.getRealms() realm = "" if realms: realm = realms[0] g.audit["user"] = t_owner or "" g.audit["realm"] = realm # --------------------------------------------------------------- -- if token.type != token_type: raise Exception( "Serial in pairing response doesn't match " "supplied token_type" ) # --------------------------------------------------------------- -- token.pair(pairing_data) g.audit["success"] = 1 g.audit["serial"] = token.getSerial() db.session.commit() return sendResult(response, False) # ------------------------------------------------------------------- -- except Exception as exx: log.error("validate/pair failed: %r", exx) g.audit["info"] = str(exx) db.session.rollback() return sendResult(response, False, 0, status=False)
def fail(self): return sendResult(response, False, 0)
def import_users(self): """ import users from a csv file into an dedicated sql resolver """ try: params = {} params.update(request.POST) # -------------------------------------------------------------- -- # processing required arguments try: data_file = request.POST['file'] resolver_name = params['resolver'] except KeyError as exx: log.exception("Missing parameter: %r", exx) raise ParameterError("Missing parameter: %r" % exx) groupid = resolver_name # process file upload data data = data_file # -- ----------------------------------------------------------- -- # In case of form post requests, it is a "instance" of FieldStorage # i.e. the Filename is selected in the browser and the data is # transferred in an iframe. # see: http://jquery.malsup.com/form/#sample4 # -- ----------------------------------------------------------- -- if isinstance(data_file, FieldStorage): data = data_file.value # -------------------------------------------------------------- -- # process the other arguments dryrun = boolean(params.get('dryrun', False)) passwords_in_plaintext = boolean( params.get('passwords_in_plaintext', False)) file_format = params.get('format', "csv") if file_format in ('password', 'passwd'): column_mapping = { "userid": 2, "username": 0, "phone": 8, "mobile": 7, "email": 9, "surname": 5, "givenname": 4, "password": 1 } format_reader = PasswdFormatReader() elif file_format in ('csv'): skip_header = boolean(params.get('skip_header', False)) if skip_header: data = '\n'.join(data.split('\n')[1:]) column_mapping = { "username": 0, "userid": 1, "surname": 2, "givenname": 3, "email": 4, "phone": 5, "mobile": 6, "password": 7 } delimiter = str(params.get('delimiter', ",")) quotechar = str(params.get('quotechar', '"')) format_reader = DefaultFormatReader() format_reader.delimiter = delimiter format_reader.quotechar = quotechar column_mapping = params.get('column_mapping', column_mapping) else: raise Exception('unspecified file foramt') # we have to convert the column_mapping back into an dict if (isinstance(column_mapping, str) or isinstance(column_mapping, unicode)): column_mapping = json.loads(column_mapping) # prevent overwrite of existing unmanaged resolver resolvers = getResolverList() if resolver_name in resolvers: if not resolvers[resolver_name].get('readonly', False): raise Exception("Unmanged resolver with same name: %r" " already exists!" % resolver_name) # -------------------------------------------------------------- -- # feed the engine :) # use a LinOTP Database context for Sessions and Engine db_context = LinOTP_DatabaseContext( SqlSession=Session, SqlEngine=linotp.model.meta.engine) # define the import into an SQL database + resolver import_handler = SQLImportHandler(groupid=groupid, resolver_name=resolver_name, database_context=db_context) # create the UserImporter with the required mapping user_import = UserImport(import_handler) user_import.set_mapping(column_mapping) # and run the data processing result = user_import.import_csv_users( data, dryrun=dryrun, format_reader=format_reader, passwords_in_plaintext=passwords_in_plaintext) if dryrun: return sendResult(response, result) # -------------------------------------------------------------- -- # create / extend target realm for the resolver resolver_spec = import_handler.get_resolver_spec() Session.commit() return sendResult(response, result) except PolicyException as pexx: log.exception("Error during user import: %r", pexx) Session.rollback() return sendError(response, "%r" % pexx, 1) except Exception as exx: log.exception("Error during user import: %r" % exx) Session.rollback() return sendError(response, "%r" % exx) finally: Session.close() log.debug('done')
def pair(self): """ validate/pair: for the enrollment of qr and push token """ try: # -------------------------------------------------------------- -- enc_response = self.request_params.get('pairing_response') if enc_response is None: raise Exception('Parameter missing') # -------------------------------------------------------------- -- dec_response = decrypt_pairing_response(enc_response) token_type = dec_response.token_type pairing_data = dec_response.pairing_data if not hasattr(pairing_data, 'serial') or \ pairing_data.serial is None: raise ValidateError('Pairing responses with no serial attached' ' are currently not implemented.') # --------------------------------------------------------------- - # TODO: pairing policy tokens = getTokens4UserOrSerial(None, pairing_data.serial) if not tokens: raise Exception('Invalid serial in pairing response') if len(tokens) > 1: raise Exception('Multiple tokens found. Pairing not possible') token = tokens[0] # prepare some audit entries t_owner = token.getUser() realms = token.getRealms() realm = '' if realms: realm = realms[0] c.audit['user'] = t_owner or '' c.audit['realm'] = realm # --------------------------------------------------------------- -- if token.type != token_type: raise Exception('Serial in pairing response doesn\'t match ' 'supplied token_type') # --------------------------------------------------------------- -- token.pair(pairing_data) c.audit['success'] = 1 Session.commit() return sendResult(response, False) # ------------------------------------------------------------------- -- except Exception as exx: log.exception("validate/pair failed: %r" % exx) c.audit['info'] = unicode(exx) Session.rollback() return sendResult(response, False, 0, status=False) finally: Session.close()
def backup(self): """ create a backup of - the encrypted token data, which could be seed or pin (if encrypted) or userpin (used in motp, ocra) - the config entries of type password the data - is encrypte with a given passphrase - and stored in an backup file (defined by the hash of backupid) :param pass: passphrase used for encrypting data in the backup file :param backupid: used to controll the intermediate backup file """ params = {} try: params.update(request.params) try: backupid = params['backupid'] passphrase = params['pass'] except KeyError as exx: raise Exception("missing Parameter:%r" % exx) backup_data = {} mig = MigrationHandler() salt = mig.setup(passphrase=passphrase) # create the backup file b_name = hashlib.sha256(backupid).digest()[:16] b_name = "%s.hbak" % binascii.hexlify(b_name) with open(b_name, 'w') as f: f.write(json.dumps({'Salt': binascii.hexlify(salt)})) f.write("\n") i = 0 for data in mig.get_config_items(): f.write(json.dumps({"Config": data})) f.write("\n") i += 1 backup_data["Config"] = i i = 0 for data in mig.get_token_data(): f.write(json.dumps({"Token": data})) f.write("\n") i += 1 backup_data["Token"] = i mac = mig.calculate_mac(json.dumps(backup_data)) f.write(json.dumps({"Counter": backup_data, 'mac': binascii.hexlify(mac)})) f.write("\n") result = {} for val in ['Token', 'Config']: result[val] = backup_data[val] return sendResult(response, result) except PolicyException as pe: log.exception('[show] policy failed: %r' % pe) return sendError(response, unicode(pe), 1) except Exception as e: log.exception('[show] failed: %r' % e) return sendError(response, e) finally: log.debug("[show] done")
def restore(self): """ restore the encrypted config and token data from a backup file the restore relies on a backup file, which was created by the migrate/backup command. The file contains per line a config or token entry, where each line is a json dump. The first line of the backup file contains the salt, the last one the number of entries written :param pass: passphrase used for encrypting data in the backup file :param backupid: used to controll the intermediate backup file """ params = {} backup_file = "" remove_backup_file = True try: params.update(request.params) try: backupid = params['backupid'] passphrase = params['pass'] remove_backup_file = ( params.get("remove_backup", "True") == "True") except KeyError as exx: raise Exception("missing Parameter:%r" % exx) mig = None # get the backup file backup_file = hashlib.sha256(backupid).digest()[:16] backup_file = "%s.hbak" % binascii.hexlify(backup_file) if not os.path.isfile(backup_file): raise Exception("No restore file found for backupid=%s" % backupid) counters = {} counter_check_done = False with open(backup_file, 'r') as f: for data in f.readlines(): if not data.strip(): # skip empty lines continue restore_data = json.loads(data) if not mig and "Salt" in restore_data: salt = restore_data["Salt"] mig = MigrationHandler() mig.setup(passphrase=passphrase, salt=binascii.unhexlify(salt)) elif "Config" in restore_data and mig: config_entry = restore_data['Config'] mig.set_config_entry(config_entry) counters["Config"] = counters.get("Config", 0) + 1 elif "Token" in restore_data and mig: token_entry = restore_data['Token'] mig.set_token_data(token_entry) counters["Token"] = counters.get("Token", 0) + 1 # Counters is the last entry - compare the counters elif "Counter" in restore_data and mig: # check inzegryty for 'number of entries' backup_data = restore_data["Counter"] mac = mig.calculate_mac(json.dumps(backup_data)) if binascii.hexlify(mac) != restore_data["mac"]: raise Exception("Restore Lines mismatch") if (restore_data["Counter"].get("Token") != counters.get("Token", 0)): raise Exception("Restore Token mismatch") if (restore_data["Counter"].get("Config") != counters.get("Config", 0)): raise Exception("Restore Config mismatch") counter_check_done = True else: if not mig: raise Exception('MigrationHandler not initialized!') else: log.info("unknown entry") # if somebody removed the last line, we cry for it if not counter_check_done: raise Exception('incomplete migration file!') return sendResult(response, counters) except PolicyException as pe: log.exception('[show] policy failed: %r' % pe) return sendError(response, unicode(pe), 1) except Exception as e: log.exception('[show] failed: %r' % e) Session.rollback() return sendError(response, e) finally: if remove_backup_file and os.path.isfile(backup_file): os.remove(backup_file) Session.close() log.debug("[restore] done")
def getmultiotp(self): """ This function is used to retrieve multiple otp values for a given user or a given serial. If the user has more than one token, the list of the tokens is returend. method: gettoken/getmultiotp arguments: serial - the serial number of the token count - number of otp values to return curTime - used ONLY for internal testing: datetime.datetime object returns: JSON response """ getotp_active = boolean(getFromConfig("linotpGetotp.active", False)) if not getotp_active: return sendError(response, "getotp is not activated.", 0) param = self.request_params ret = {} try: serial = getParam(param, "serial", required) count = int(getParam(param, "count", required)) curTime = getParam(param, "curTime", optional) view = getParam(param, "view", optional) r1 = checkPolicyPre("admin", "getotp", param) log.debug("[getmultiotp] admin-getotp policy: %s", r1) max_count = checkPolicyPre("gettoken", "max_count", param) log.debug("[getmultiotp] maxcount policy: %s", max_count) if count > max_count: count = max_count log.debug("[getmultiotp] retrieving OTP value for token %s", serial) ret = get_multi_otp(serial, count=int(count), curTime=curTime) ret["serial"] = serial g.audit["success"] = True db.session.commit() if view: c.ret = ret return render("/selfservice/multiotp_view.mako").decode( "utf-8") else: return sendResult(response, ret, 0) except PolicyException as pe: log.error("[getotp] gettoken/getotp policy failed: %r", pe) db.session.rollback() return sendError(response, pe, 1) except Exception as exx: log.error("[getmultiotp] gettoken/getmultiotp failed: %r", exx) db.session.rollback() return sendError(response, "gettoken/getmultiotp failed: %r" % exx, 0)