def authenticate(identifier, password): """Given an e-mail address (or Yubikey OTP) and password, authenticate a user.""" ts = time() # Record the query = dict(active=True) # Gracefully handle extended characters in passwords. # The password storage algorithm works in binary. if isinstance(password, unicode): password = password.encode('utf8') # Build the MongoEngine query to find if '@' in identifier: query[b'email'] = identifier elif len(identifier) == 44: query[b'otp'] = identifier[:12] else: query[b'username'] = identifier user = User.objects(**query).first() if not user or not User.password.check(user.password, password) or (user.rotp and len(user.otp) != 0 and not 'otp' in query): if user: LoginHistory(user, False, request.remote_addr).save() # Prevent basic timing attacks; always take at least one second to process. sleep(max(min(1 - (time() - ts), 0), 1)) return None # Validate Yubikey OTP if 'otp' in query: client = yubico.Yubico( config['yubico.client'], config['yubico.key'], boolean(config.get('yubico.secure', False)) ) try: status = client.verify(identifier, return_response=True) except: return None if not status: return None user.update(set__seen=datetime.utcnow()) # Record the fact the user signed in. LoginHistory(user, True, request.remote_addr).save() return user.id, user
def authenticate(cls, identifier, password): query = dict(active=True) if isinstance(password, unicode): password = password.encode('utf8') if '@' in identifier: query[b'email'] = identifier elif len(identifier) == 44: query[b'otp'] = identifier[:12] else: query[b'username'] = identifier user = cls.objects(**query).first() ts = time() if not user or not User.password.check(user.password, password): if user: LoginHistory(user, False, web.core.request.remote_addr).save() # Prevent basic timing attacks; always take at least one second to process. sleep(max(min(1 - (time() - ts), 0), 1)) return None # Validate OTP if 'otp' in query: client = yubico.Yubico( web.core.config['yubico.client'], web.core.config['yubico.key'], True if web.core.config.get( 'yubico.secure', False) == 'True' else False) try: status = client.verify(identifier, return_response=True) except: return None if not status: return None user.update(set__seen=datetime.utcnow()) LoginHistory(user, True, web.core.request.remote_addr).save() return user.id, user
import sys import crypt import ldap, ldap.sasl from yubico import yubico import traceback CLIENT_ID = '1' CLIENT_KEY = '' LDAP_YUBI_ATTR = 'yubikeyID' LDAP_PASSWD_ATTR = 'passwordFactor' VALIDATOR = yubico.Yubico(CLIENT_ID, CLIENT_KEY, verify_cert=True) def ldap_connect(): con = ldap.initialize('ldapi:///') try: con.sasl_interactive_bind_s('', ldap.sasl.external()) except: print >> sys.stderr, "Unable to bind to LDAP" traceback.print_exception(*sys.exc_info()) return None return con def get_user_info(user): """Returns a pair (hashedpass, yubikeyid) for the user""" con = ldap_connect() if not con: return None
from yubico import yubico_exceptions client_id = raw_input('Enter your client id: ') secret_key = raw_input('Enter your secret key (optional): ') use_https = raw_input('Use secure connection (https)? [y/n]: ') token = raw_input('Enter OTP token: ') if not secret_key: secret_key = None if use_https == 'n': https = False else: https = True client = yubico.Yubico(client_id, secret_key, https) try: status = client.verify(token) except yubico_exceptions.InvalidClientIdError, e: print 'Client with id %s does not exist' % (e.client_id) sys.exit(1) except yubico_exceptions.SignatureVerificationError: print 'Signature verification failed' sys.exit(1) except yubico_exceptions.StatusCodeError, e: print 'Negative status code was returned: %s' % (e.status_code) sys.exit(1) if status: print 'Success, the provided OTP is valid'
def post(self, **post): try: data = Bunch(post) except Exception as e: if config.get('debug', False): raise return 'json:', dict(success=False, message=_("Unable to parse data."), data=post, exc=str(e)) query = dict(active=True) query[b'username'] = data.id user = User.objects(**query).first() if data.form == "changepassword": passwd_ok, error_msg = _check_password(data.passwd, data.passwd1) if not passwd_ok: return 'json:', dict(success=False, message=error_msg, data=data) if isinstance(data.old, unicode): data.old = data.old.encode('utf-8') if not User.password.check(user.password, data.old): return 'json:', dict(success=False, message=_("Old password incorrect."), data=data) #If the password isn't strong enough, reject it if (zxcvbn.password_strength(data.passwd).get("score") < MINIMUM_PASSWORD_STRENGTH): return 'json:', dict( success=False, message= _("Password provided is too weak. please add more characters, or include lowercase, uppercase, and special characters." ), data=data) user.password = data.passwd user.save() elif data.form == "addotp": if isinstance(data.password, unicode): data.password = data.password.encode('utf-8') identifier = data.otp client = yubico.Yubico(config['yubico.client'], config['yubico.key'], boolean(config.get('yubico.secure', False))) if not User.password.check(user.password, data.password): return 'json:', dict(success=False, message=_("Password incorrect."), data=data) try: status = client.verify(identifier, return_response=True) except: return 'json:', dict(success=False, message=_("Failed to contact YubiCloud."), data=data) if not status: return 'json:', dict(success=False, message=_("Failed to verify key."), data=data) if not User.addOTP(user, identifier[:12]): return 'json:', dict(success=False, message=_("YubiKey already exists."), data=data) elif data.form == "removeotp": identifier = data.otp if not User.removeOTP(user, identifier[:12]): return 'json:', dict(success=False, message=_("YubiKey invalid."), data=data) elif data.form == "configureotp": if isinstance(data.password, unicode): data.password = data.password.encode('utf-8') rotp = True if 'rotp' in data else False if not User.password.check(user.password, data.password): return 'json:', dict(success=False, message=_("Password incorrect."), data=data) user.rotp = rotp user.save() #Handle the user attempting to delete their account elif data.form == "deleteaccount": if isinstance(data.passwd, unicode): data.passwd = data.passwd.encode('utf-8') #Make the user enter their username so they know what they're doing. if not user.username == data.username.lower(): return 'json:', dict(success=False, message=_("Username incorrect."), data=data) #Check whether the user's supplied password is correct if not User.password.check(user.password, data.passwd): return 'json:', dict(success=False, message=_("Password incorrect."), data=data) #Make them type "delete" exactly if not data.confirm == "delete": return 'json:', dict( success=False, message=_( "Delete was either misspelled or not lowercase."), data=data) #Delete the user account and then deauthenticate the browser session log.info("User %s authorized the deletion of their account.", user) user.delete() deauthenticate() #Redirect user to the root of the server instead of the settings page return 'json:', dict(success=True, location="/") #Handle the user attempting to change the email address associated with their account elif data.form == "changeemail": if isinstance(data.passwd, unicode): data.passwd = data.passwd.encode('utf-8') #Check whether the user's supplied password is correct if not User.password.check(user.password, data.passwd): return 'json:', dict(success=False, message=_("Password incorrect."), data=data) #Check that the two provided email addresses match if not data.newEmail.lower() == data.newEmailConfirm.lower(): return 'json:', dict( success=False, message=_("Provided email addresses do not match."), data=data) #Make sure that the provided email address is a valid form for an email address v = EmailValidator() email = data.newEmail email, err = v.validate(email) if err: return 'json:', dict( success=False, message=_("Invalid email address provided."), data=data) #Make sure that the new email address is not already taken count = User.objects.filter(**{ "email": data.newEmail.lower() }).count() if not count == 0: return 'json:', dict( success=False, message=_("The email address provided is already taken."), data=data) #Change the email address in the database and catch any email validation exceptions that mongo throws user.email = data.newEmail.lower() try: user.save() except ValidationError: return 'json:', dict( success=False, message=_("Invalid email address provided."), data=data) except NotUniqueError: return 'json:', dict( success=False, message=_("The email address provided is already taken."), data=data) #Handle the user attempting to merge 2 accounts together elif data.form == "mergeaccount": if isinstance(data.passwd, unicode): data.passwd = data.passwd.encode('utf-8') if isinstance(data.passwd2, unicode): data.passwd2 = data.passwd2.encode('utf-8') #Make the user enter their username so they know what they're doing. if user.username != data.username.lower( ) and user.username != data.username: return 'json:', dict(success=False, message=_("First username incorrect."), data=data) #Check whether the user's supplied password is correct if not User.password.check(user.password, data.passwd): return 'json:', dict(success=False, message=_("First password incorrect."), data=data) #Make sure the user isn't trying to merge their account into itself. if data.username.lower() == data.username2.lower(): return 'json:', dict( success=False, message=_("You can't merge an account into itself."), data=data) #Make the user enter the second username so we can get the User object they want merged in. if not User.objects( username=data.username2.lower()) and not User.objects( username=data.username2): return 'json:', dict( success=False, message=_("Unable to find user by second username."), data=data) other = User.objects(username=data.username2).first() if not other: other = User.objects(username=data.username2.lower()).first() #Check whether the user's supplied password is correct if not User.password.check(other.password, data.passwd2): return 'json:', dict(success=False, message=_("Second password incorrect."), data=data) #Make them type "merge" exactly if data.confirm != "merge": return 'json:', dict( success=False, message=_("Merge was either misspelled or not lowercase."), data=data) log.info("User %s merged account %s into %s.", user.username, other.username, user.username) user.merge(other) #Redirect user to the root of the server instead of the settings page return 'json:', dict(success=True, location="/") else: return 'json:', dict(success=False, message=_("Form does not exist."), location="/") return 'json:', dict(success=True, location="/account/settings")