def test_check_token_invalid_timestamp_disabled_security(self): ''' With disabled timestamp security, invalid timestamps should still succeed. ''' now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() past = now - datetime.timedelta(seconds=settings.TIMESTAMP_DURATION + 1) past_token = utils.make_token('user', 'secr3t', nonce, past) future = now + datetime.timedelta(seconds=settings.DRIFT_OFFSET + 1) nonce = utils._random_string() future_token = utils.make_token('user', 'secr3t', nonce, future) with mock.patch.object(settings, 'SECURITY_CHECK_TIMESTAMP', False): try: self.assertEqual( utils.check_token(past_token, lambda x: 'secr3t'), 'user') except exceptions.InvalidTimestamp: self.fail( 'InvalidTimestamp raised with expired timestamp and ' + 'timestamp security disabled.') try: self.assertEqual( utils.check_token(future_token, lambda x: 'secr3t'), 'user') except exceptions.InvalidTimestamp: self.fail( 'InvalidTimestamp raised with expired timestamp and ' + 'timestamp security disabled.')
def test_random_string_length(self): ''' The length of a generated string should match what is specified or the default length. ''' self.assertEqual(len(utils._random_string(10)), 10) self.assertEqual(len(utils._random_string()), settings.NONCE_LENGTH) self.assertEqual(len(utils._random_string(settings.NONCE_LENGTH + 1)), settings.NONCE_LENGTH + 1)
def test_random_string_is_random(self): ''' Strings generated in sequence should not clash. ''' strings = [utils._random_string() for _ in range(25)] self.assertEqual(len(set(strings)), len(strings))
def test_check_token(self): ''' Check a token that was just generated. ''' now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, now) self.assertEqual(utils.check_token(token, lambda x: 'secr3t'), 'user')
def test_check_token_invalid_password(self): ''' Check a valid token with an invalid password. ''' now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() token = utils.make_token('user', 'wrong password', nonce, now) self.assertIsNone(utils.check_token(token, lambda x: 'secr3t'))
def test_check_token_long_nonce(self): ''' Check a token with a long nonce - it should be rejected. ''' now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() + 'a' token = utils.make_token('user', 'secr3t', nonce, now) with self.assertRaises(exceptions.InvalidNonce): utils.check_token(token, lambda x: 'secr3t')
def test_check_token_short_nonce(self): ''' Check a token with a short nonce - it should be rejected. ''' now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string(length=settings.NONCE_LENGTH - 1) token = utils.make_token('user', 'secr3t', nonce, now) with self.assertRaises(exceptions.InvalidNonce): utils.check_token(token, lambda x: 'secr3t')
def test_random_string_chars(self): ''' The characters in the string should only contain those in the specified list. ''' allowed_chars = 'abcde' string = utils._random_string(allowed_chars=allowed_chars) for c in string: self.assertIn(c, allowed_chars)
def test_check_token_replay_attack(self): ''' Check a token twice - a replay attack should be detected. ''' now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, now) utils.check_token(token, lambda x: 'secr3t') with self.assertRaises(exceptions.InvalidNonce): utils.check_token(token, lambda x: 'secr3t')
def test_check_token_expired_timestamp(self): ''' Check a token that has an expired timestamp. An error should be raised. ''' ts = (datetime.datetime.utcnow().replace(tzinfo=utc.utc) - datetime.timedelta(seconds=settings.TIMESTAMP_DURATION + 1)) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, ts) with self.assertRaises(exceptions.InvalidTimestamp): utils.check_token(token, lambda x: 'secr3t')
def test_check_token_drift(self): ''' Check a token that has a timestamp with drift. The user should still be authenticated. ''' ts = (datetime.datetime.utcnow().replace(tzinfo=utc.utc) + datetime.timedelta(seconds=settings.DRIFT_OFFSET - 1)) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, ts) self.assertEqual(utils.check_token(token, lambda x: 'secr3t'), 'user')
def test_check_token_nonexistent_user(self): ''' Check a valid token but with a user does not exist. An error should be raised. ''' users = {'username': None} now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, now) with self.assertRaises(exceptions.UserException): utils.check_token(token, lambda x: users[x])
def test_check_token_future_timestamp(self): ''' Check a token that has a timestamp in the future. An error should be raised. ''' ts = (datetime.datetime.utcnow().replace(tzinfo=utc.utc) + datetime.timedelta(seconds=settings.DRIFT_OFFSET + 1)) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, ts) with self.assertRaises(exceptions.InvalidTimestamp): utils.check_token(token, lambda x: 'secr3t')
def test_check_token_invalid_timestamp_format(self): ''' Check a token that has a timestamp in an invalid format. ''' ts = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, ts, ts_format='prefix-{}-suffix'.format( settings.TIMESTAMP_FORMATS[0])) with self.assertRaises(exceptions.InvalidTimestamp): utils.check_token(token, lambda x: 'secr3t')
def test_check_token_prohibited_algorithms(self): ''' Check a valid token with prohibited algorithms. An error should be raised. ''' for algorithm in settings.PROHIBITED_DIGEST_ALGORITHMS: now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, now, algorithm=algorithm.lower()) with self.assertRaises(exceptions.AlgorithmProhibited): utils.check_token(token, lambda x: 'secr3t')
def test_check_token_alterntive_timestamp_format(self): ''' Check a token that was just generated with a timestamp in a different format. ''' for fmt in settings.TIMESTAMP_FORMATS[1:]: now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, now, ts_format=fmt) self.assertEqual(utils.check_token(token, lambda x: 'secr3t'), 'user')
def test_check_token_alternative_algorithm(self): ''' Check a valid token with an alternative algorithm. ''' now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, now, algorithm='sha512') with mock.patch.object(settings, 'ALLOWED_DIGEST_ALGORITHMS', ['SHA256', 'SHA512']): self.assertEqual(utils.check_token(token, lambda x: 'secr3t'), 'user')
def test_check_token_replay_attack_disabled_security(self): ''' Check a token twice, but with nonce security disabled. A replay attack should not be detected. ''' now = datetime.datetime.utcnow().replace(tzinfo=utc.utc) nonce = utils._random_string() token = utils.make_token('user', 'secr3t', nonce, now) with mock.patch.object(settings, 'SECURITY_CHECK_NONCE', False): self.assertEqual(utils.check_token(token, lambda x: 'secr3t'), 'user') try: self.assertEqual(utils.check_token(token, lambda x: 'secr3t'), 'user') except exceptions.InvalidNonce: self.fail('InvalidNonce raised with nonce security disabled.')
def make_header_values(self, user=None, username=None, timestamp=None, digest=None, b64_digest=None, nonce=None, b64_nonce=None, digest_algorithm=None): ''' Make the header values from the given parameters. :param user: (optional) user to authenticate with header :type user: django.contrib.auth.models.User :param username: (optional) username to provide in header :type username: str :param timestamp: (optional) timestamp to use in header :type timestamp: str :param digest: (optional) header digest :type digest: bytes :param b64_digest: (optional) header digest as base64 :type b64_digest: bytes :param nonce: (optional) header nonce :type nonce: bytes :param b64_nonce: (optional) header nonce as base64 :type b64_nonce: bytes :param digest_algorithm: (optional, default: sha256) digest algorithm to use. It must be supported by hashlib. :type digest_algorithm: str :return: WSSE authentication header parts :rtype: dict ''' if user is None: user = self.user if username is None: username = user.username if timestamp is None: now = timezone.now() timestamp = now.strftime(settings.TIMESTAMP_FORMATS[0]) if nonce is None: nonce = utils._random_string(length=settings.NONCE_LENGTH) if digest is None: digest = utils._b64_digest(nonce, timestamp, self.user_secret.secret, algorithm=digest_algorithm) if b64_nonce is None: b64_nonce = base64.b64encode(utils._to_bytes(nonce)) if b64_digest is not None: digest = b64_digest header_values = { 'Username': username, 'PasswordDigest': utils._from_bytes(digest), 'Nonce': utils._from_bytes(b64_nonce), 'Created': timestamp } return header_values