Projet

Général

Profil

0001-auth_oidc-dont-stop-redirect-on-state-lost-47825.patch

Benjamin Dauvergne, 18 octobre 2020 12:56

Télécharger (4,5 ko)

Voir les différences:

Subject: [PATCH] auth_oidc: dont stop redirect on state lost (#47825)

Next url is usually to a local view requiring login, so on state lost we
can just continue to original next_url and it will start a new login.
 src/authentic2_auth_oidc/views.py | 15 +++++++++------
 tests/test_auth_oidc.py           | 23 +++++++++++++++++++++++
 2 files changed, 32 insertions(+), 6 deletions(-)
src/authentic2_auth_oidc/views.py
41 41
    provider = get_provider(pk)
42 42
    scopes = set(provider.scopes.split()) | set(['openid'])
43 43
    state = str(uuid.uuid4())
44
    next_url = next_url or request.GET.get(REDIRECT_FIELD_NAME, '')
45
    if next_url and not good_next_url(request, next_url):
46
        next_url = None
44 47
    nonce = request.GET.get('nonce') or str(uuid.uuid4())
45 48
    display = set()
46 49
    prompt = set()
......
49 52
        'scope': ' '.join(scopes),
50 53
        'response_type': 'code',
51 54
        'redirect_uri': request.build_absolute_uri(reverse('oidc-login-callback')),
52
        'state': state,
55
        'state': state if not next_url else state + ' ' + next_url,
53 56
        'nonce': nonce,
54 57
    }
55 58
    if provider.claims_parameter_supported:
......
73 76
    saved_state = request.session.setdefault('auth_oidc', {}).setdefault(state, {})
74 77
    saved_state['request'] = params
75 78
    saved_state['issuer'] = provider.issuer
76
    next_url = next_url or request.GET.get(REDIRECT_FIELD_NAME, '')
77
    if good_next_url(request, next_url):
79
    if next_url:
78 80
        saved_state['next_url'] = next_url
79 81
    request.session.modified = True  # necessary if auth_oidc already exists
80 82
    logger.debug('auth_oidc: sent request to authorization endpoint %r', params)
......
102 104
    def get(self, request, *args, **kwargs):
103 105
        logger = logging.getLogger(__name__)
104 106
        code = request.GET.get('code')
105
        state = request.GET.get('state')
107
        raw_state = request.GET.get('state')
108
        state = raw_state.split(' ', 1)[0]
106 109
        oidc_state = self.oidc_state = request.session.get('auth_oidc', {}).get(state)
107 110
        if not state or not oidc_state or 'request' not in oidc_state:
108
            messages.warning(request, _('Login with OpenIDConnect failed, state lost.'))
109 111
            logger.warning('auth_oidc: state lost')
110
            return redirect(request, settings.LOGIN_REDIRECT_URL)
112
            state_next_url = raw_state.split(' ', 1)[1] if ' ' in raw_state else None
113
            return redirect(request, state_next_url or settings.LOGIN_REDIRECT_URL)
111 114
        oidc_request = oidc_state.get('request')
112 115
        assert isinstance(oidc_request, dict), 'state is not properly initialized'
113 116
        nonce = oidc_request.get('nonce')
tests/test_auth_oidc.py
822 822
    assert user.last_name == 'DOE'
823 823
    # typo in template string, no rendering
824 824
    assert user.first_name == '{{ given_name'
825

  
826

  
827
def test_lost_state(app, caplog, code, oidc_provider, oidc_provider_jwkset, hooks):
828
    response = app.get('/login/?next=/whatever/')
829
    assert oidc_provider.name in response.text
830
    response = response.click(oidc_provider.name)
831
    qs = urlparse.parse_qs(urlparse.urlparse(response.location).query)
832
    state = qs['state'][0]
833
    assert ' /whatever/' in state
834
    nonce = app.session['auth_oidc'][state.split()[0]]['request']['nonce']
835

  
836
    # reset the session to forget the state
837
    app.session.flush()
838

  
839
    caplog.clear()
840
    with oidc_provider_mock(oidc_provider, oidc_provider_jwkset, code, nonce=nonce):
841
        response = app.get(login_callback_url(oidc_provider), params={'code': code, 'state': state})
842
    # not logged
843
    assert 'auth_oidc: state lost' == caplog.records[-1].message
844
    # event is recorded
845
    assert '_auth_user_id' not in app.session
846
    # we are automatically redirected to our destination
847
    assert response.location == '/whatever/'
825
-