예제 #1
0
class TestWSSEAuth(StaticLiveServerTestCase):
    '''
	Test authenticating through the `WSSEAuth` handler.
	'''
    endpoint = reverse_lazy('api-test')

    def setUp(self):
        '''
		Set up the test cases.
		'''
        self.user = User.objects.create(username='******')
        self.user_secret = UserSecret.objects.create(user=self.user)

        self.auth = WSSEAuth('username', self.user_secret.secret)
        self.base_url = '{}{}'.format(self.live_server_url, self.endpoint)

    def test_auth(self):
        '''
		Perform valid authentication. The user should be authenticated.
		'''
        response = requests.get(self.base_url, auth=self.auth)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_auth_reuse(self):
        '''
		Reuse the same authentication handler. Both requests should succeed.
		'''
        response_a = requests.get(self.base_url, auth=self.auth)
        response_b = requests.get(self.base_url, auth=self.auth)

        self.assertEqual(response_a.status_code, status.HTTP_200_OK)
        self.assertEqual(response_b.status_code, status.HTTP_200_OK)

    def test_auth_incorrect_password(self):
        '''
		Authneticate with an incorrect password. The authentication should fail.
		'''
        response = requests.get(self.base_url,
                                auth=WSSEAuth('username',
                                              '!' + self.user_secret.secret))
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_auth_nonexistent_username(self):
        '''
		Authneticate with a nonexistent user. The authentication should fail.
		'''
        response = requests.get(self.base_url,
                                auth=WSSEAuth('nonexistentuser',
                                              self.user_secret.secret))
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
예제 #2
0
class TestWSSEAuthenticatedHTTPTransport(StaticLiveServerTestCase):
    '''
	Test authenticating through the `WSSEAuthenticatedHTTPTransport` transport.
	'''
    endpoint = reverse_lazy('api-test')

    def setUp(self):
        '''
		Set up the test cases.
		'''
        self.user = User.objects.create(username='******')
        self.user_secret = UserSecret.objects.create(user=self.user)

        self.client = self.makeClient('username', self.user_secret.secret)
        self.base_url = '{}{}'.format(self.live_server_url, self.endpoint)

    def makeClient(self, username, password, *args, **kwargs):
        '''
		Make a coreapi client using the username/password authentication.

		:param username: username to authenticate as
		:type username: str

		:param password: password to authenticate with
		:type password: str

		:return: client that uses specified authentication
		:rtype: coreapi.Client
		'''
        wsse_transport = WSSEAuthenticatedHTTPTransport(
            username, password, *args, **kwargs)
        return Client(transports=[wsse_transport])

    def test_auth(self):
        '''
		Perform valid authentication. The user should be authenticated.
		'''
        try:
            response = self.client.get(self.base_url)
        except ErrorMessage as e:
            self.fail('Client.get failed with %s' % (str(e), ))

    def test_auth_reuse(self):
        '''
		Reuse the same authentication handler. Both requests should succeed.
		'''
        try:
            response_a = self.client.get(self.base_url)
            response_b = self.client.get(self.base_url)
        except ErrorMessage as e:
            self.fail('Client.get failed with %s' % (str(e), ))

    def test_auth_incorrect_password(self):
        '''
		Authneticate with an incorrect password. The authentication should fail.
		'''
        client = self.makeClient('username', '!' + self.user_secret.secret)

        with self.assertRaises(ErrorMessage) as e:
            response = client.get(self.base_url)

            self.assertEqual(e.exception.error.title, 'Unauthorized')

    def test_auth_nonexistent_username(self):
        '''
		Authneticate with a nonexistent user. The authentication should fail.
		'''
        client = self.makeClient('nonexistentuser', self.user_secret.secret)

        with self.assertRaises(ErrorMessage) as e:
            response = client.get(self.base_url)

            self.assertEqual(e.exception.error.title, 'Unauthorized')
예제 #3
0
class WSSEAuthenticationTests(APITestCase):
    '''
	Test WSSE Authentication on the API.
	'''
    factory = APIRequestFactory()
    base_url = reverse_lazy('api-test')

    @contextlib.contextmanager
    def http_auth(self, header):
        '''
		Perform HTTP authentication, through headers, in a request.
		The headers are automatically cleared afterwards.
		'''
        kwargs = {utils._django_header(settings.REQUEST_HEADER): header}
        self.client.credentials(**kwargs)
        yield

        # Clear the credential headers.
        self.client.credentials()

    @classmethod
    def setUpClass(cls):
        '''
		Set up the class for running tests.
		'''
        cls.user = User.objects.create(username='******')
        cls.user_secret = UserSecret.objects.create(user=cls.user)

    @classmethod
    def tearDownClass(cls):
        '''
		Tear down the class after running tests.
		'''
        cls.user.delete()

    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

    def make_header(self, *args, **kwargs):
        '''
		Make the header from the given values.

		:return: WSSE authentication header
		:rtype: str
		'''
        header_values = self.make_header_values(*args, **kwargs)
        header = (', '.join('{k}="{v}"'.format(k=k, v=v)
                            for k, v in header_values.items()))
        return header

    def test_valid_authentication(self):
        '''
		Authenticate with a valid username. The authentication should succeed.
		'''
        with self.http_auth(self.make_header()):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_valid_authentication_alternative_timestamp_format(self):
        '''
		Authenticate with a valid username, using an alternative timestamp format.
		The authentication should succeed.
		'''
        now = timezone.now()
        timestamp = now.strftime('%Y-%m-%dT%H:%M:%S.%fZ')

        with self.http_auth(self.make_header(timestamp=timestamp)):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_valid_authentication_alternative_headers(self):
        '''
		Make a valid authentication request. Use various permutations of the
		header format.
		'''
        default_params = ['Username', 'PasswordDigest', 'Nonce', 'Created']

        for params in itertools.permutations(default_params):
            header_values = self.make_header_values()
            header = (
                'UsernameToken ' +
                ', '.join('{k}="{v}"'.format(k=param, v=header_values[param])
                          for param in params))
            with self.http_auth(header):
                response = self.client.get(self.base_url)

            self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_valid_authentication_drift(self):
        '''
		Authenticate with a valid username with drift on the timestamp.
		The authentication should succeed.
		'''
        ts = (timezone.now() +
              datetime.timedelta(seconds=settings.DRIFT_OFFSET - 1))
        timestamp = ts.strftime('%Y-%m-%dT%H:%M:%S.%fZ')

        with self.http_auth(self.make_header()):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)

    def test_no_authentication(self):
        '''
		Perform a request with no attempt at authentication. Authentication
		should not succeed.
		'''
        response = self.client.get(self.base_url)
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_wrong_format_authentication(self):
        '''
		Perform a request with incorrect authentication header format.
		Authentication should not succeed.
		'''
        with self.http_auth('WrongFormat=27'):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_invalid_timestamp_authentication(self):
        '''
		Perform a request with an invalid timestamp.
		Authentication should not succeed.
		'''
        with self.http_auth(self.make_header(timestamp='Nope')):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_invalid_timestamp_format_authentication(self):
        '''
		Perform a request with an invalid timestamp format.
		Authentication should not succeed.
		'''
        now = timezone.now()
        timestamp = now.strftime("%m/%d/%Y, %M:%S.%f")

        with self.http_auth(self.make_header(timestamp=timestamp)):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_expired_timestamp(self):
        '''
		Authenticate an expired timestamp. The authentication should not succeed.
		'''
        now = timezone.now() - datetime.timedelta(
            seconds=settings.TIMESTAMP_DURATION + 1)
        timestamp = now.strftime('%Y-%m-%dT%H:%M:%S.%fZ')

        with self.http_auth(self.make_header(timestamp=timestamp)):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_future_timestamp(self):
        '''
		Authenticate a future timestamp. The authentication should not succeed.
		'''
        now = timezone.now() + datetime.timedelta(
            seconds=settings.TIMESTAMP_DURATION + 1)
        timestamp = now.strftime('%Y-%m-%dT%H:%M:%S.%fZ')

        with self.http_auth(self.make_header(timestamp=timestamp)):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_incorrect_username(self):
        '''
		Authenticate with an incorrect username. The authetication should not
		succeed.
		'''
        with self.http_auth(self.make_header(username='******')):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_invalid_b64_nonce(self):
        '''
		Authenticate with a nonce that is not base64. The authentication should not
		succeed.
		'''
        with self.http_auth(self.make_header(b64_nonce='?????????')):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_short_nonce(self):
        '''
		Authenticate with a nonce that is fewer than 8 characters. The
		authentication should not succeed.
		'''
        with self.http_auth(self.make_header(b64_nonce='short')):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_long_nonce(self):
        '''
		Authenticate with a nonce that is longer than 32 characters. The
		authentication should not succeed.
		'''
        with self.http_auth(self.make_header(b64_nonce='a' * 72)):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_authenticate_sha1(self):
        '''
		Authenticate with a valid header, but calculate the digest using SHA-1.
		The authentication should not succeed.
		'''
        with self.http_auth(self.make_header(digest_algorithm='sha1')):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_invalid_digest(self):
        '''
		Authenticate with an invalid digest. The authentication should not succeed.
		'''
        with self.http_auth(self.make_header(digest='nope'.encode('utf-8'))):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_invalid_digest_b64(self):
        '''
		Authenticate with an invalid digest, in base64.
		The authentication should not succeed.
		'''
        with self.http_auth(self.make_header(b64_digest='nope')):
            response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

    def test_replay_attack(self):
        '''
		Authenticate with a valid header twice. The second authentication should
		be detected as a replay attack.
		'''
        header = self.make_header()

        with self.http_auth(header):
            response = self.client.get(self.base_url)
        with self.http_auth(header):
            second_response = self.client.get(self.base_url)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(second_response.status_code,
                         status.HTTP_401_UNAUTHORIZED)

    def test_replay_attack_multiple(self):
        '''
		Authenticate with a valid header multiple times.
		The following authentication attempts should be detected as replay attacks.
		'''
        header = self.make_header()

        with self.http_auth(header):
            response = self.client.get(self.base_url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        for _ in range(10):
            with self.http_auth(header):
                new_resp = self.client.get(self.base_url)

            self.assertEqual(new_resp.status_code,
                             status.HTTP_401_UNAUTHORIZED)