class ValidateController(BaseController): ''' The privacyidea.controllers are the implementation of the web-API to talk to the privacyIDEA server. The ValidateController is used to validate the username with its given OTP value. An Authentication module like pam_privacyidea or rlm_privacyidea uses this ValidateController. The functions of the ValidateController are invoked like this https://server/validate/<functionname> The functions are described below in more detail. ''' @log_with(log) def __before__(self, action, **params): try: c.audit['client'] = get_client() self.Policy = PolicyClass(request, config, c, get_privacyIDEA_config(), token_type_list = get_token_type_list()) return response except Exception as exx: log.error("exception %r" % (action, exx)) log.error(traceback.format_exc()) Session.rollback() Session.close() return sendError(response, exx, context='before') finally: pass @log_with(log) def __after__(self, action, **params): self.audit.log(c.audit) return response @log_with(log) def _check(self, param): ''' basic check function, that can be used by different controllers :param param: dict of all caller parameters :type param: dict :return: Tuple of True or False and opt :rtype: Tuple(boolean, opt) ''' opt = None options = {} ## put everythin in the options but the user, pass, init options.update(param) for para in ["pass", "user", "init"]: if options.has_key(para): del options[para] passw = getParam(param, "pass", optional) user = getUserFromParam(param, optional) # support for ocra application challenge verification challenge = getParam(param, "challenge", optional) if challenge is not None: options = {} options['challenge'] = challenge c.audit['user'] = user.login realm = user.realm or getDefaultRealm() c.audit['realm'] = realm # AUTHORIZATION Pre Check # we need to overwrite the user.realm in case the user does not exist in the original realm (setrealm-policy) user.realm = self.Policy.set_realm(user.login, realm, exception=True) self.Policy.check_user_authorization(user.login, user.realm, exception=True) if isSelfTest() == True: initTime = getParam(param, "init", optional) if initTime is not None: if options is None: options = {} options['initTime'] = initTime (ok, opt) = checkUserPass(user, passw, options=options) c.audit['success'] = ok if ok: # AUTHORIZATION post check toks = getTokens4UserOrSerial(None, c.audit["serial"]) if len(toks) > 0 and c.audit["serial"]: # This might be empty in case of passOnNoToken ttype = toks[0].getType().lower() self.Policy.check_auth_tokentype(ttype, exception=True, user=user) self.Policy.check_auth_serial(c.audit['serial'], exception=True, user=user) # add additional details if self.Policy.is_auth_return(ok, user=user): if opt == None: opt = {} if ok: opt['realm'] = c.audit.get('realm') opt['user'] = c.audit.get('user') opt['tokentype'] = c.audit.get('token_type') opt['serial'] = c.audit.get('serial') else: opt['error'] = c.audit.get('action_detail') return (ok, opt) # @profile_decorator(log_file="/tmp/validate.prof") @log_with(log) def check(self, action, **params): ''' 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": "privacyIDEA 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 = {} ok = False opt = None try: param.update(request.params) # 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("authorization failed for validate/check: %r" % exx) c.audit['success'] = False c.audit['info'] = unicode(exx) ok = False if self.Policy.is_auth_return(ok): if opt == None: opt = {} opt['error'] = c.audit.get('info') Session.commit() qr = getParam(param, 'qr', optional) if qr is not None and opt is not None and opt.has_key('message'): try: dataobj = opt.get('message') param['alt'] = "%s" % opt 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.error("validate/check failed: %r" % exx) log.error(traceback.format_exc()) # 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 sendError(response, u"validate/check failed: %s" % unicode(exx), 0) finally: Session.close() @log_with(log) def check_yubikey(self, action, **params): ''' 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": "privacyIDEA 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: ok, opt = checkYubikeyPass(passw) c.audit['success'] = ok except AuthorizeException as exx: log.warning("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.error("validate/check_yubikey failed: %r" % exx) log.error(traceback.format_exc()) c.audit['info'] = unicode(exx) Session.rollback() return sendError(response, u"validate/check_yubikey failed: %s" % unicode(exx), 0) finally: Session.close() @log_with(log) def check_url(self, action, **params): ''' 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("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.error("webob.exception %r" % acc) log.error(traceback.format_exc()) Session.rollback() raise acc except Exception as exx: log.error("validate/check_url failed: %r" % exx) log.error(traceback.format_exc()) Session.rollback() return sendError(response, u"validate/check_url failed: %s" % unicode(exx), 0) finally: Session.close() @log_with(log) def samlcheck(self, action, **params): ''' This function is used to validate the username and the otp value/password in a SAML environment. If ``privacyidea.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("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("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("validate/check failed: %r" % exx) log.error(traceback.format_exc()) Session.rollback() return sendError(response, "validate/samlcheck failed: %s" % unicode(exx), 0) finally: Session.close() @log_with(log) def check_t(self, action, **params): param = {} value = {} ok = False opt = None try: param.update(request.params) passw = getParam(param, "pass", required) 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!") serial = get_tokenserial_of_transaction(transId=transid) if serial is None: value['value'] = False value['failure'] = 'No challenge for transaction %r found'\ % transid else: param['serial'] = serial tokens = getTokens4UserOrSerial(serial=serial) if len(tokens) == 0 or len(tokens) > 1: raise Exception('tokenmismatch for token serial: %s' % (unicode(serial))) theToken = tokens[0] tok = theToken.token realms = tok.getRealmNames() if realms is None or len(realms) == 0: realm = getDefaultRealm() elif len(realms) > 0: realm = realms[0] userInfo = getUserInfo(tok.privacyIDEAUserid, tok.privacyIDEAIdResolver, tok.privacyIDEAIdResClass) user = User(login=userInfo.get('username'), realm=realm) (ok, opt) = checkSerialPass(serial, passw, user=user, options=param) value['value'] = ok failcount = theToken.getFailCount() value['failcount'] = int(failcount) c.audit['success'] = ok #c.audit['info'] += "%s=%s, " % (k, value) Session.commit() qr = getParam(param, 'qr', optional) if qr is not None and opt is not None and opt.has_key('message'): try: dataobj = opt.get('message') param['alt'] = "%s" % opt 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.error("validate/check_t failed: %r" % exx) log.error(traceback.format_exc()) c.audit['info'] = unicode(exx) Session.rollback() return sendError(response, "validate/check_t failed: %s" % unicode(exx), 0) finally: Session.close() @log_with(log) def check_s(self, action, **params): ''' 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 = {} param.update(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() == True: options['initTime'] = param.get('init') try: passw = getParam(param, "pass", optional) serial = getParam(param, 'serial', optional) if serial is None: user = getParam(param, 'user', optional) if user is not None: user = getUserFromParam(param, optional) 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('privacyIDEA.RealmNames') if realms is None or len(realms) == 0: realm = getDefaultRealm() elif len(realms) > 0: realm = realms[0] userInfo = getUserInfo(tok.privacyIDEAUserid, tok.privacyIDEAIdResolver, tok.privacyIDEAIdResClass) user = User(login=userInfo.get('username'), realm=realm) serial = tok.getSerial() c.audit['serial'] = serial if isSelfTest() == True: initTime = getParam(param, "init", optional) if initTime is not None: if options is None: options = {} options['initTime'] = initTime (ok, opt) = checkSerialPass(serial, passw, options=options) c.audit['success'] = ok Session.commit() qr = getParam(param, 'qr', optional) if qr is not None and opt is not None and opt.has_key('message'): try: dataobj = opt.get('message') param['alt'] = "%s" % opt 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.error("validate/check_s failed: %r" % exx) log.error(traceback.format_exc()) c.audit['info'] = unicode(exx) Session.rollback() return sendError(response, "validate/check_s failed: %s" % unicode(exx), 0) finally: Session.close() @log_with(log) def simplecheck(self, action, **params): ''' This function is used to validate the username and the otp value/password. method: validate/simplecheck arguments: * user: username / loginname * pass: the password that consists of a possible fixes password component and the OTP value * realm: additional realm to match the user to a useridresolver returns: Simple ascii response: :-) in case of success :-( in case of failed authentication :-/ in case of any error ''' opt = None param = request.params res = [] try: try: (ok, opt) = self._check(param) except AuthorizeException as e: log.warning("validate/simplecheck: %r" % e) c.audit['success'] = False c.audit['action_detail'] = unicode(e) ok = False Session.commit() if ok == True: ret = u":-)" else: ret = u":-(" res.append(ret) if opt != None: stat = opt.get('transactionid') or opt.get('state') or "" res.append(stat) msg = opt.get('data') or opt.get('message') or "" res.append(msg) #TODO: implement serialize additional info of check return " ".join(res) except Exception as exx: log.error("failed: %r" % exx) log.error(traceback.format_exc()) Session.rollback() return u":-/" finally: Session.close() def ok(self): return sendResult(response, "TRUE", 0) def fail(self): return sendResult(response, "FALSE", 0) @log_with(log) def smspin(self, action, **params): ''' 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 = request.params state = '' message = 'No sms message defined!' try: user = getUserFromParam(param, optional) 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.error("validate/smspin failed: %r" % exx) log.error(traceback.format_exc()) # 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 sendError(response, "validate/smspin failed: %s" % unicode(exx), 0) finally: Session.close()
def authenticate_privacyidea_user(user, realm, password): ''' this function performs an authentication against the privacyidea server. :param user: Username of the user :type user: string :return: In case of success return the username :rtype: string ''' res = False success = None Policy = PolicyClass(request, config, c, get_privacyIDEA_config()) if Policy.check_user_authorization(user, realm, exception=False): ''' We SHOULD do it this way, but unfortunately we only get the complete context in a web request. We are missing the client and the HSM! (res, _opt) = checkUserPass(User(login=user, realm=realm), password) Big FIXME: The server is asking himself... :-/ ''' # we need to pass the client= to cope with client dependent policies. # Otherwise the authentication request will have the client 127.0.0.1 # as the source. client = request.client_addr data = urllib.urlencode({'user': user, 'realm': realm, 'pass': password, 'client': client}) url = ini_config.get("privacyideaURL") + "/validate/check" disable_ssl = ini_config.get("privacyideaURL.disable_ssl", False) headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"} try: # is httplib compiled with ssl? http = httplib2.\ Http(disable_ssl_certificate_validation=disable_ssl) except TypeError as exx: # not so on squeeze: # TypeError: __init__() got an unexpected keyword argument # 'disable_ssl_certificate_validation' log.warning("httplib2 'disable_ssl_certificate_validation' " "attribute error: %r" % exx) # so we run in fallback mode http = httplib2.Http() (_resp, content) = http.request(url, method="POST", body=data, headers=headers) rv = json.loads(content) if rv.get("result"): # in case of normal json output res = rv['result'].get('value', False) if res: success = "%s@%s" % (user, realm) return success