def _get_sorted_passwords(lock_id): type_ordinal = { PasswordType.UNLIMITED: 0, PasswordType.OTP: 1, } passwords = DB.child("Locks").child(lock_id).child( "passwords").get().val() or {} passwords_list = [(pw_id, password_dict) for pw_id, password_dict in passwords.items()] passwords_list = sorted( passwords_list, key=lambda x: type_ordinal[PasswordType(x[1]['type'])]) passwords_list = [ Password.from_database(id, password_dict) for id, password_dict in passwords_list ] expired_passwords = [] output = [] for password in passwords_list: if password.expiration != -1 \ and password.expiration < time_utils.get_current_time_ms(): expired_passwords.append(password) elif _password_is_active(password): output.append(password) _prune_passwords(lock_id, expired_passwords) return output
def get_passwords(lock_id): passwords = get_lock(lock_id).get('passwords', {}) result = { 'otp': [], 'permanent': [], } for pw_id, password in passwords.items(): password_type = PasswordType(password['type']) if password_type == PasswordType.OTP: result['otp'].append(Password.from_database(pw_id, password)) elif password_type == PasswordType.UNLIMITED: result['permanent'].append(Password.from_database(pw_id, password)) else: raise AppException("Error: a password entry is malformed.") return result
def test_put_lock_status_open_requested_correct_password_otp( client, id_token, seeded_lock, seeded_password, # A permanent password seed_password, get_mock_password, db, mocker): pw_str = "192168" otp_password = Password(type=PasswordType.OTP, password=get_mock_password(pw_str, hashed=True)) otp_password = seed_password(password=otp_password, lock=seeded_lock) lock_id = seeded_lock.id response = client.put('/api/v1/locks/{}/status'.format(lock_id), headers={ 'Authorization': id_token, }, json={ 'status': LockStatus.OPEN_REQUESTED.value, 'password': pw_str }) active_password_keys = db.child('Locks').child(lock_id).child( 'passwords').get().val().keys() assert otp_password.id not in active_password_keys assert response.status_code == 200 assert response.get_json() == { 'status': LockStatus.OPEN_REQUESTED.value, 'providedPasswordDisabled': True }
def post(self, uid, user, **kwargs): lock_id = kwargs['lockId'] security_utils.verify_lock_ownership(uid, lock_id) kwargs['password'] = security_utils.hash_password(kwargs['password']) password = Password.build(kwargs) return (password_manager.add_password(lock_id, password), LockPasswordResponse.code)
def test_put_lock_status_open_requested_password_not_active_day( client, id_token, seeded_lock, seeded_password, # A permanent password seed_password, get_mock_password, db, mocker): # Feb 11 2019 is a Monday # NOTE: the lock is located in eastern time, GMT-5 # (see document_templates.Lock.timezone) with freeze_time("Feb 11th, 2019 23:59:59", tz_offset=0): pw_str = "192168" expired_pw = Password(type=PasswordType.UNLIMITED, active_days=[PasswordDays.MONDAY], password=get_mock_password(pw_str, hashed=True)) expired_pw = seed_password(password=expired_pw, lock=seeded_lock) # One second before Tuesday in eastern time with freeze_time("Feb 12th, 2019 04:59:59", tz_offset=0): lock_id = seeded_lock.id response = client.put('/api/v1/locks/{}/status'.format(lock_id), headers={ 'Authorization': id_token, }, json={ 'status': LockStatus.OPEN_REQUESTED.value, 'password': pw_str }) assert response.status_code == 200 # Exactly on Tuesday with freeze_time("Feb 12th, 2019 05:00:00", tz_offset=0): lock_id = seeded_lock.id response = client.put('/api/v1/locks/{}/status'.format(lock_id), headers={ 'Authorization': id_token, }, json={ 'status': LockStatus.OPEN_REQUESTED.value, 'password': pw_str }) assert response.status_code == 401 assert response.get_json() == { "error": "Invalid or inactive password supplied" } # Expired passwords should be removed from the database # after PUT request active_password_keys = db.child('Locks').child(lock_id).child( 'passwords').get().val().keys() # Both passwords should be in the DB still assert expired_pw.id in active_password_keys assert seeded_password.id in active_password_keys
def add_password(lock_id, password: Password) -> PasswordMetadata: new_id = DB.child("Locks").child(lock_id).child("passwords").push( password.serialize())['name'] # return a metadata object instead of a password to ensure that the # password is not shared with the outside world return PasswordMetadata( type=password.type, expiration=password.expiration, active_days=password.active_days, active_times=password.active_times, id=new_id, )
def test_put_lock_status_open_requested_expired_password( client, id_token, seeded_lock, seeded_password, # A permanent password seed_password, get_mock_password, db, mocker): with freeze_time("Feb 2nd, 2019") as frozen_time: pw_str = "192168" expired_pw = Password(type=PasswordType.UNLIMITED, expiration=time_utils.get_current_time_ms(), password=get_mock_password(pw_str, hashed=True)) expired_pw = seed_password(password=expired_pw, lock=seeded_lock) frozen_time.tick() lock_id = seeded_lock.id response = client.put('/api/v1/locks/{}/status'.format(lock_id), headers={ 'Authorization': id_token, }, json={ 'status': LockStatus.OPEN_REQUESTED.value, 'password': pw_str }) assert response.status_code == 401 assert response.get_json() == { "error": "Invalid or inactive password supplied" } # Expired passwords should be removed from the database # after PUT request active_password_keys = db.child('Locks').child(lock_id).child( 'passwords').get().val().keys() assert expired_pw.id not in active_password_keys assert seeded_password.id in active_password_keys
def mock_password(get_mock_password, default_password) -> Password: return Password( type=PasswordType.UNLIMITED, password=get_mock_password(default_password, hashed=True), )
def actual(password: Password, lock: Lock): new_id = db.child("Locks").child(lock.id).child("passwords").push( password.serialize())['name'] password.id = new_id return password
def get_password(lock_id, password_id): found_password = _get_password_from_db(lock_id, password_id) if found_password: return Password.from_database(password_id, found_password)