예제 #1
0
def test_login_timeout(mocker, requests_mock):
    store = {}
    handler = indieauth.IndieAuth('http://client/', tokens.DictStore(store),
                                  10)

    mock_time = mocker.patch('time.time')
    mock_time.return_value = 238742

    requests_mock.get(
        'http://example.user/',
        text='hello',
        headers={'Link': '<http://endpoint/>; rel="authorization_endpoint"'})

    assert len(store) == 0
    response = handler.initiate_auth('http://example.user', 'http://client/cb',
                                     '/dest')
    assert isinstance(response, disposition.Redirect)
    assert len(store) == 1

    mock_time.return_value += 100

    data = {'state': parse_args(response.url)['state'], 'code': 'bogus'}
    response = handler.check_callback('http://client/cb', data, {})
    assert isinstance(response, disposition.Error)
    assert 'timed out' in response.message
    assert len(store) == 0
예제 #2
0
def test_security_requirements():
    with pytest.raises(Exception):
        indieauth.IndieAuth('foobarbaz', tokens.Serializer('qwerpoiu'))
예제 #3
0
def test_handler_failures(requests_mock):
    store = {}
    handler = indieauth.IndieAuth('http://client/', tokens.DictStore(store),
                                  10)

    # Attempt to auth against page with no endpoint
    requests_mock.get('http://no-endpoint/', text='hello')
    response = handler.initiate_auth('http://no-endpoint/', 'http://cb/',
                                     'bogus')
    assert isinstance(response, disposition.Error)
    assert 'endpoint' in response.message
    assert len(store) == 0

    # Attempt to inject a transaction-less callback response
    response = handler.check_callback('http://no-transaction', {}, {})
    assert isinstance(response, disposition.Error)
    assert 'No transaction' in response.message
    assert len(store) == 0

    # Attempt to inject a forged callback response
    response = handler.check_callback('http://bogus-transaction',
                                      {'state': 'bogus'}, {})
    assert isinstance(response, disposition.Error)
    assert 'Invalid token' in response.message
    assert len(store) == 0

    # Get a valid state token
    requests_mock.get(
        'http://example.user/',
        text='hello',
        headers={'Link': '<http://endpoint/>; rel="authorization_endpoint"'})

    response = handler.initiate_auth('http://example.user', 'http://client/cb',
                                     '/dest')
    assert isinstance(response, disposition.Redirect)
    data = {'state': parse_args(response.url)['state']}
    assert len(store) == 1

    # no code assigned
    assert "Missing 'code'" in handler.check_callback('http://client/cb', data,
                                                      {}).message
    assert len(store) == 0

    def check_failure(message):
        assert len(store) == 0
        response = handler.initiate_auth('http://example.user',
                                         'http://client/cb', '/dest')
        assert isinstance(response, disposition.Redirect)
        assert len(store) == 1
        data = {'state': parse_args(response.url)['state'], 'code': 'bogus'}
        response = handler.check_callback('http://client/cb', data, {})
        assert isinstance(response, disposition.Error)
        assert message in response.message
        assert len(store) == 0

    # callback returns error
    requests_mock.post('http://endpoint/', status_code=400)
    check_failure('returned 400')

    # callback returns broken JSON
    requests_mock.post('http://endpoint/', text='invalid json')
    check_failure('invalid response JSON')

    # callback returns a page with no endpoint
    requests_mock.post('http://endpoint/', json={'me': 'http://empty.user'})
    requests_mock.get('http://empty.user', text='hello')
    check_failure('missing IndieAuth endpoint')

    # callback returns a page with a different endpoint
    requests_mock.post('http://endpoint/',
                       json={'me': 'http://different.user'})
    requests_mock.get(
        'http://different.user',
        headers={
            'Link': '<http://otherendpoint/>; rel="authorization_endpoint"'
        })
    check_failure('Authorization endpoint mismatch')
예제 #4
0
def test_handler_success(requests_mock):
    store = {}
    handler = indieauth.IndieAuth('http://client/', tokens.DictStore(store))

    assert handler.service_name == 'IndieAuth'
    assert handler.url_schemes
    assert 'IndieAuth' in handler.description
    assert handler.cb_id
    assert handler.logo_html[0][1] == 'IndieAuth'

    # profile page at http://example.user/ which redirects to https://example.user/bob
    endpoint = {
        'Link': '<https://auth.example/endpoint>; rel="authorization_endpoint'
    }
    requests_mock.get('http://example.user/', headers=endpoint)
    requests_mock.get('https://example.user/bob', headers=endpoint)

    injected = requests.get('http://example.user/')

    # it should not handle the URL on its own
    assert not handler.handles_url('http://example.user/')
    assert handler.handles_page('http://example.user/', injected.headers,
                                BeautifulSoup(injected.text, 'html.parser'),
                                injected.links)

    # and now the URL should be cached
    assert handler.handles_url('http://example.user/')

    disp = handler.initiate_auth('http://example.user/', 'http://client/cb',
                                 '/dest')
    assert isinstance(disp, disposition.Redirect)
    assert disp.url.startswith('https://auth.example/endpoint')

    # fake the user dialog on the IndieAuth endpoint
    user_get = parse_args(disp.url)
    assert user_get['redirect_uri'].startswith('http://client/cb')
    assert 'client_id' in user_get
    assert 'state' in user_get
    assert user_get['state'] in store
    assert user_get['response_type'] == 'code'
    assert 'me' in user_get
    challenge = user_get['code_challenge']
    assert user_get['code_challenge_method'] == 'S256'

    # fake the verification response
    def verify_callback(request, _):
        import urllib.parse
        args = urllib.parse.parse_qs(request.text)
        assert args['code'] == ['asdf']
        assert args['client_id'] == ['http://client/']
        assert 'redirect_uri' in args
        verifier = args['code_verifier'][0]
        assert utils.pkce_challenge(verifier) == challenge
        return json.dumps({
            'me': 'https://example.user/bob',
            'profile': {
                'email': '*****@*****.**',
                'url': 'https://bob.example.user/'
            }
        })

    requests_mock.post('https://auth.example/endpoint', text=verify_callback)

    LOGGER.debug("state=%s", user_get['state'])
    response = handler.check_callback(user_get['redirect_uri'], {
        'state': user_get['state'],
        'code': 'asdf',
    }, {})
    LOGGER.debug("verification response: %s", response)
    assert isinstance(response, disposition.Verified)
    assert response.identity == 'https://example.user/bob'
    assert response.redir == '/dest'

    for key, val in {
            # provided by profile scope
            'homepage': 'https://bob.example.user/',
            'email': '*****@*****.**',
    }.items():
        assert response.profile[key] == val

    # trying to replay the same transaction should fail
    response = handler.check_callback(user_get['redirect_uri'], {
        'state': user_get['state'],
        'code': 'asdf',
    }, {})
    assert isinstance(response, disposition.Error)