def _do_code_exchange(self, request,  # type: Dict[str, str]
                          extra_id_token_claims=None
                          # type: Optional[Union[Mapping[str, Union[str, List[str]]], Callable[[str, str], Mapping[str, Union[str, List[str]]]]]
                          ):
        # type: (...) -> oic.message.AccessTokenResponse
        """
        Handles a token request for exchanging an authorization code for an access token
        (grant_type=authorization_code).
        :param request: parsed http request parameters
        :param extra_id_token_claims: any extra parameters to include in the signed ID Token, either as a dict-like
            object or as a callable object accepting the local user identifier and client identifier which returns
            any extra claims which might depend on the user id and/or client id.
        :return: a token response containing a signed ID Token, an Access Token, and a Refresh Token
        :raise InvalidTokenRequest: if the token request is invalid
        """
        token_request = AccessTokenRequest().from_dict(request)
        try:
            token_request.verify()
        except MessageException as e:
            raise InvalidTokenRequest(str(e), token_request) from e

        authentication_request = self.authz_state.get_authorization_request_for_code(token_request['code'])

        if token_request['client_id'] != authentication_request['client_id']:
            logger.info('Authorization code \'%s\' belonging to \'%s\' was used by \'%s\'',
                        token_request['code'], authentication_request['client_id'], token_request['client_id'])
            raise InvalidAuthorizationCode('{} unknown'.format(token_request['code']))
        if token_request['redirect_uri'] != authentication_request['redirect_uri']:
            raise InvalidTokenRequest('Invalid redirect_uri: {} != {}'.format(token_request['redirect_uri'],
                                                                              authentication_request['redirect_uri']),
                                      token_request)

        sub = self.authz_state.get_subject_identifier_for_code(token_request['code'])
        user_id = self.authz_state.get_user_id_for_subject_identifier(sub)

        response = AccessTokenResponse()

        access_token = self.authz_state.exchange_code_for_token(token_request['code'])
        self._add_access_token_to_response(response, access_token)
        refresh_token = self.authz_state.create_refresh_token(access_token.value)
        if refresh_token is not None:
            response['refresh_token'] = refresh_token

        if extra_id_token_claims is None:
            extra_id_token_claims = {}
        elif callable(extra_id_token_claims):
            extra_id_token_claims = extra_id_token_claims(user_id, authentication_request['client_id'])
        requested_claims = self._get_requested_claims_in(authentication_request, 'id_token')
        user_claims = self.userinfo.get_claims_for(user_id, requested_claims)
        response['id_token'] = self._create_signed_id_token(authentication_request['client_id'], sub,
                                                            user_claims,
                                                            authentication_request.get('nonce'),
                                                            None, access_token.value,
                                                            extra_id_token_claims)
        logger.debug('issued id_token=%s from requested_claims=%s userinfo=%s extra_claims=%s',
                     response['id_token'], requested_claims, user_claims, extra_id_token_claims)

        return response
Handles a token request for exchanging an authorization code for an access token
        (grant_type=authorization_code).
        :param request: parsed http request parameters
        :param extra_id_token_claims: any extra parameters to include in the signed ID Token, either as a dict-like
            object or as a callable object accepting the local user identifier and client identifier which returns
            any extra claims which might depend on the user id and/or client id.
        :return: a token response containing a signed ID Token, an Access Token, and a Refresh Token
        :raise InvalidTokenRequest: if the token request is invalid