def authenticate_totp(self, environ, auth_user): totp_challenger = SecurityTOTP.get_for_user(auth_user) # if there is no totp configured, don't allow auth # shouldn't happen, login flow should create a totp_challenger if totp_challenger is None: log.info("Login attempted without MFA configured for: {}".format( auth_user)) return None request = Request(environ, charset='utf-8') if not ('mfa' in request.POST): log.info("Could not get MFA credentials from the request") return None try: result = totp_challenger.check_code(request.POST['mfa']) except ReplayAttackException as e: log.warning( "Detected a possible replay attack for user: {}, context: {}". format(auth_user, e)) return None if result: return auth_user
def _setup_totp_template_variables(self, context, data_dict): """Populates context with is_sysadmin totp_challenger_uri totp_secret mfa_test_valid """ c.is_sysadmin = authz.is_sysadmin(c.user) c.totp_user_id = data_dict['id'] user_dict = self._fetch_user_or_fail(context, data_dict) c.user_dict = user_dict c.is_myself = user_dict['name'] == c.user totp_challenger = SecurityTOTP.get_for_user(user_dict['name']) if totp_challenger is not None: c.totp_secret = totp_challenger.secret c.totp_challenger_uri = totp_challenger.provisioning_uri mfa_test_code = request.params.get('mfa') if request.method == 'POST' and mfa_test_code is not None: c.mfa_test_valid = totp_challenger.check_code(mfa_test_code, verify_only=True) c.mfa_test_invalid = not c.mfa_test_valid
def new(self, id=None): """Set up a users new security TOTP credentials""" context = { 'model': model, 'session': model.Session, 'user': c.user, 'auth_user_obj': c.userobj } # pylons includes the rest of the url in the param, so we need to strip the /new suffix user_id = id.replace('/new', '') data_dict = {'id': user_id, 'user_obj': c.userobj} user_dict = self._fetch_user_or_fail(context, data_dict) SecurityTOTP.create_for_user(user_dict['name']) self._setup_totp_template_variables(context, data_dict) log.info("Rotated the 2fa secret for user {}".format(user_id)) helpers.flash_success(_('Successfully updated two factor authentication secret. Make sure you add the new secret to your authenticator app.')) helpers.redirect_to('mfa_configure', id=user_id)
def reset_totp(self, username): print('Resetting totp secret for user', username) from ckanext.security.model import SecurityTOTP SecurityTOTP.create_for_user(username) print('Success!')
def login(self): """ Ajax call to test username/password/mfa code """ tk.response.headers.update({'Content-Type': 'application/json'}) try: res = {} if request.method != 'POST': tk.response.status_int = 405 return json.dumps(res) identity = request.params if not ('login' in identity and 'password' in identity): tk.response.status_int = 422 return json.dumps(res) user_name = identity['login'] user = model.User.by_name(user_name) login_throttle_key = get_login_throttle_key(request, user_name) if login_throttle_key is None: tk.response.status_int = 403 return json.dumps(res) throttle = LoginThrottle(user, login_throttle_key) locked_out = throttle.is_locked() if locked_out: log.info( 'User {} attempted login while brute force lockout in place' .format(user_name)) invalid_login = user is None or not user.is_active( ) or not user.validate_password(identity['password']) if invalid_login: # Increment the throttle counter if the login failed. throttle.increment() if locked_out or invalid_login: log.info('login failed') tk.response.status_int = 403 return json.dumps(res) # find or create 2 factor auth record totp_challenger = SecurityTOTP.get_for_user(user.name) if totp_challenger is None: totp_challenger = SecurityTOTP.create_for_user(user.name) mfaConfigured = totp_challenger.last_successful_challenge is not None if not mfaConfigured: res['totpSecret'] = totp_challenger.secret res['totpChallengerURI'] = totp_challenger.provisioning_uri res['mfaConfigured'] = mfaConfigured if identity['mfa']: res['mfaCodeValid'] = totp_challenger.check_code( identity['mfa'], verify_only=True) return json.dumps(res) except Exception as err: log.error('Unhandled error during login: {}'.format(err)) tk.response.status_int = 500 return json.dumps({})
def login(self): """ Ajax call to test username/password/mfa code """ def set_response(status): tk.response.status_int = status tk.response.headers.update({'Content-Type': 'application/json'}) try: res = {} if request.method != 'POST': set_response(405) return json.dumps(res) identity = request.params if not ('login' in identity and 'password' in identity): set_response(422) return json.dumps(res) on_mfa_form = identity.get('mfa-form-active') == 'true' user_name = identity['login'] user = model.User.by_name(user_name) login_throttle_key = get_login_throttle_key(request, user_name) if login_throttle_key is None: set_response(403) return json.dumps(res) throttle = LoginThrottle(user, login_throttle_key) locked_out = throttle.is_locked() if locked_out: log.info('User %s attempted login while brute force lockout in place', user_name) invalid_login = user is None or not user.is_active() or not user.validate_password(identity['password']) if invalid_login: # Increment the throttle counter if the login failed. throttle.increment() if invalid_login or (locked_out and not on_mfa_form): log.info('login failed for %s', user_name) set_response(403) return json.dumps(res) # find or create 2 factor auth record totp_challenger = SecurityTOTP.get_for_user(user.name) if totp_challenger is None: totp_challenger = SecurityTOTP.create_for_user(user.name) mfaConfigured = totp_challenger.last_successful_challenge is not None if not mfaConfigured: res['totpSecret'] = totp_challenger.secret res['totpChallengerURI'] = totp_challenger.provisioning_uri res['mfaConfigured'] = mfaConfigured set_response(200) if identity['mfa']: code_valid = totp_challenger.check_code(identity['mfa'], verify_only=True) res['mfaCodeValid'] = code_valid and not locked_out if code_valid: log.info('Login succeeded for %s', user_name) else: log.info('User %s supplied invalid 2fa code', user_name) set_response(403) throttle.increment() return json.dumps(res) except Exception as err: log.error('Unhandled error during login: %s', err) set_response(500) return json.dumps({})