taverntesting / tavern

A command-line tool and Python library and Pytest plugin for automated testing of RESTful APIs, with a simple, concise and flexible YAML-based syntax
https://taverntesting.github.io/
MIT License
1.03k stars 195 forks source link

2FA Log In #868

Closed MorganFujimaka closed 1 year ago

MorganFujimaka commented 1 year ago

What would be the best strategy to test API having a 2FA login (e.g. Okta via Authlib)?

Is it possible to log in using django.test.Client rather than API and have the logged-in user in the following stages?:

from django.test import Client

client.login(username, password)
michaelboulton commented 1 year ago

I haven't used Django for a while but I think there's 2 options to do this: Either write a fixture that logs in using the django client and get the cookies:

import http.cookies
from django.test import Client

def django_cookies(client: Client) -> http.cookies.SimpleCookie:
    client.login("username", "password")

    return client.cookies
---
test_name: Use Django client to create cookies

marks:
  - usefixtures: django_login

stages:
  - name: Do a request with cookies
    request:
      url: "{host}/thing"
      method: GET
    cookies:
      - my_cookie_name: "{django_cookies.my_cookie_name}"

If that doesn't work, you'd probably have to write a plugin that logged in via the Django authentication system and then used the client to do all future http requests

MorganFujimaka commented 1 year ago

Hi @michaelboulton, thank you for the prompt reply.

I haven't tried writing a custom plugin, but passing a sessionid in cookies works like a charm. The issue is that I have many tests, and passing the cookies with every request looks tedious. I tried to use the pytest_tavern_beta_before_every_request hook, but it didn't work.

So, I decided to mock OAuth:

from unittest import mock

class MockOAuth():
    class MockOktaUserInfoResponse():
        def __init__(self, email):
            self.email = email

        def raise_for_status(*args, **kwargs):
            return None

        def json(self):
            if self.email:
                return { "email": self.email }
            else:
                return {}

    class MockOkta():
        def authorize_access_token(self, request):
            return request.POST.get("email")

        def get(self, userinfo_uri, token):
            return MockOAuth.MockOktaUserInfoResponse(email=token)

    def __init__(self):
        self.okta = MockOAuth.MockOkta()

    def register(*args, **kwargs):
        return True

@pytest.fixture(autouse=True)
def mock_okta():
    with mock.patch("authlib.integrations.django_client.OAuth", new=MockOAuth):
        yield