Пример #1
0
    def validate_params(self):
        # Client validation.
        try:
            self.client = Client.objects.get(client_id=self.params['client_id'])
        except Client.DoesNotExist:
            logger.debug('[Authorize] Invalid client identifier: %s', self.params['client_id'])
            raise ClientIdError()

        # Redirect URI validation.
        if self.is_authentication and not self.params['redirect_uri']:
            logger.debug('[Authorize] Missing redirect uri.')
            raise RedirectUriError()
        clean_redirect_uri = urlsplit(self.params['redirect_uri'])
        clean_redirect_uri = urlunsplit(clean_redirect_uri._replace(query=''))
        if not (clean_redirect_uri in self.client.redirect_uris):
            logger.debug('[Authorize] Invalid redirect uri: %s', self.params['redirect_uri'])
            raise RedirectUriError()

        # Grant type validation.
        if not self.grant_type:
            logger.debug('[Authorize] Invalid response type: %s', self.params['response_type'])
            raise AuthorizeError(self.params['redirect_uri'], 'unsupported_response_type', self.grant_type)

        # Nonce parameter validation.
        if self.is_authentication and self.grant_type == 'implicit' and not self.params['nonce']:
            raise AuthorizeError(self.params['redirect_uri'], 'invalid_request', self.grant_type)

        # Response type parameter validation.
        if self.is_authentication and self.params['response_type'] != self.client.response_type:
            raise AuthorizeError(self.params['redirect_uri'], 'invalid_request', self.grant_type)

        # PKCE validation of the transformation method.
        if self.params['code_challenge']:
            if not (self.params['code_challenge_method'] in ['plain', 'S256']):
                raise AuthorizeError(self.params['redirect_uri'], 'invalid_request', self.grant_type)
Пример #2
0
 def test_no_params(self):
     """Test with a path only and no query/frag params"""
     redirect_uri = self.redirect_uri + 'path'
     error = AuthorizeError(redirect_uri, self.error, self.grant_type)
     created_uri = error.create_uri(redirect_uri, '')
     expected_uri = '{}?error={}&error_description={}'.format(
         redirect_uri, self.error, self.desc)
     compare(expected_uri, created_uri)
Пример #3
0
 def test_query_params_only(self):
     """Test with query param in redirect uri"""
     redirect_uri = self.redirect_uri + "path/?action=something"
     error = AuthorizeError(redirect_uri, self.error, self.grant_type)
     created_uri = error.create_uri(redirect_uri, '')
     expected_uri = '{}&error={}&error_description={}'.format(
         redirect_uri, self.error, self.desc)
     compare(expected_uri, created_uri)
Пример #4
0
 def test_with_deep_link(self):
     """Test with a non-http schema; deep link style (think slack://)"""
     redirect_uri = 'slack://example.com/path'
     state = 'my_state'
     error = AuthorizeError(redirect_uri, self.error, self.grant_type)
     created_uri = error.create_uri(redirect_uri, state)
     expected_uri = '{}?error={}&error_description={}&state={}' \
         .format(redirect_uri, self.error, self.desc, state)
     compare(expected_uri, created_uri)
Пример #5
0
 def test_with_state(self):
     """Test with state"""
     redirect_uri = self.redirect_uri + 'path'
     state = 'my_state'
     error = AuthorizeError(redirect_uri, self.error, self.grant_type)
     created_uri = error.create_uri(redirect_uri, state)
     expected_uri = '{}path?error={}&error_description={}&state={}' \
         .format(self.redirect_uri, self.error, self.desc, state)
     compare(expected_uri, created_uri)
Пример #6
0
 def test_query_and_frag_params(self):
     """Test with both qp's and fragment"""
     redirect_uri = self.redirect_uri + 'path?my_qp=test'
     frag = '#action=something'
     error = AuthorizeError(redirect_uri + frag, self.error,
                            self.grant_type)
     created_uri = error.create_uri(redirect_uri + frag, '')
     expected_uri = '{}path?my_qp=test&error={}&error_description={}{}' \
         .format(self.redirect_uri, self.error, self.desc, frag)
     compare(expected_uri, created_uri)
Пример #7
0
 def test_frag_params_only(self):
     """Test with fragment params only"""
     redirect_uri = self.redirect_uri + 'path'
     frag = '#action=something'
     error = AuthorizeError(redirect_uri + frag, self.error,
                            self.grant_type)
     created_uri = error.create_uri(redirect_uri + frag, '')
     expected_uri = '{}path?error={}&error_description={}{}'.format(
         self.redirect_uri, self.error, self.desc, frag)
     compare(expected_uri, created_uri)
Пример #8
0
    def validate_params(self):
        # Client validation.
        try:
            self.client = self.client_class.objects.get(
                client_id=self.params['client_id'])
        except Client.DoesNotExist:
            logger.debug('[Authorize] Invalid client identifier: %s',
                         self.params['client_id'])
            raise ClientIdError()

        # Redirect URI validation.
        if self.is_authentication and not self.params['redirect_uri']:
            logger.debug('[Authorize] Missing redirect uri.')
            raise RedirectUriError()
        if not redirect_uri_valid(self.params['redirect_uri'],
                                  self.client.redirect_uris):
            logger.debug('[Authorize] Invalid redirect uri: %s',
                         self.params['redirect_uri'])
            raise RedirectUriError()

        # Grant type validation.
        if not self.grant_type:
            logger.debug('[Authorize] Invalid response type: %s',
                         self.params['response_type'])
            raise AuthorizeError(self.params['redirect_uri'],
                                 'unsupported_response_type', self.grant_type)

        if (not self.is_authentication and
            (self.grant_type == 'hybrid' or self.params['response_type']
             in ['id_token', 'id_token token'])):
            logger.debug('[Authorize] Missing openid scope.')
            raise AuthorizeError(self.params['redirect_uri'], 'invalid_scope',
                                 self.grant_type)

        # Nonce parameter validation.
        if self.is_authentication and self.grant_type == 'implicit' and not self.params[
                'nonce']:
            raise AuthorizeError(self.params['redirect_uri'],
                                 'invalid_request', self.grant_type)

        # Response type parameter validation.
        if self.is_authentication \
                and self.params['response_type'] not in self.client.response_type_values():
            raise AuthorizeError(self.params['redirect_uri'],
                                 'invalid_request', self.grant_type)

        # PKCE validation of the transformation method.
        if self.params['code_challenge']:
            if not (self.params['code_challenge_method'] in ['plain', 'S256']):
                raise AuthorizeError(self.params['redirect_uri'],
                                     'invalid_request', self.grant_type)
Пример #9
0
    def post(self, request, *args, **kwargs):
        authorize = AuthorizeEndpoint(request)

        try:
            authorize.validate_params()

            if not request.POST.get('allow'):
                signals.user_decline_consent.send(
                    self.__class__,
                    user=request.user,
                    client=authorize.client,
                    scope=authorize.params['scope'])

                raise AuthorizeError(authorize.params['redirect_uri'],
                                     'access_denied', authorize.grant_type)

            signals.user_accept_consent.send(self.__class__,
                                             user=request.user,
                                             client=authorize.client,
                                             scope=authorize.params['scope'])

            # Save the user consent given to the client.
            authorize.set_client_user_consent()

            uri = authorize.create_response_uri()

            return redirect(uri)

        except AuthorizeError as error:
            uri = error.create_uri(authorize.params['redirect_uri'],
                                   authorize.params['state'])

            return redirect(uri)
Пример #10
0
    def test_invalid_params(self, mocker, api_client):
        mocker.patch.object(AuthorizeEndpoint, "__init__", return_value=None)
        mocker.patch.object(
            AuthorizeEndpoint,
            "validate_params",
            side_effect=AuthorizeError("err", "desc", "grant_type"),
        )

        response = api_client.get(
            "/vc/connect/authorize/?pres_req_conf_id=pid&scope=some_scope vc_authn"
        )
        assert response.status_code == status.HTTP_400_BAD_REQUEST
Пример #11
0
def check_login_required(request, authorize):
    """
    Raises AuthorizeError or JustRedirect if login is required for this auth.
    """
    if 'login' in authorize.params['prompt'] or request.user.is_anonymous:
        if 'none' in authorize.params['prompt']:
            raise AuthorizeError(authorize.params['redirect_uri'],
                                 'login_required', authorize.grant_type)
        else:
            django_user_logout(request)
            full_path = strip_prompt_login(request.get_full_path())
            raise JustRedirect(
                redirect_to_login(full_path, settings.get('OIDC_LOGIN_URL')))
Пример #12
0
    def post(self, request, *args, **kwargs):
        authorize = self.authorize_endpoint_class(request)

        try:
            authorize.validate_params()

            check_login_required(request, authorize)

            if not request.POST.get('allow'):
                signals.user_decline_consent.send(
                    self.__class__,
                    user=request.user,
                    client=authorize.client,
                    scope=authorize.params['scope'])

                call_oidc_declined_userconsent_hook(request, authorize.client)

                raise AuthorizeError(authorize.params['redirect_uri'],
                                     'access_denied', authorize.grant_type)

            signals.user_accept_consent.send(self.__class__,
                                             user=request.user,
                                             client=authorize.client,
                                             scope=authorize.params['scope'])

            # Save the user consent given to the client.
            authorize.set_client_user_consent()

            uri = authorize.create_response_uri()

            call_oidc_prior_to_redirect_hook(request, authorize.client)
            return redirect(uri)

        except AuthorizeError as error:
            uri = error.create_uri(authorize.params['redirect_uri'],
                                   authorize.params['state'])

            return redirect(uri)
        except JustRedirect as error:
            # TODO we're trying to redirect to a login page here, but can't at this point as
            # 1. We don't have all the query parameters
            # 2. The nonce is wrong.
            # So just display an error.
            context = {
                'error':
                "Logged out",
                'description':
                "You have logged out or your session has timed out. Please re-try the authorization.",
            }

            return render(request, OIDC_TEMPLATES['error'], context)
Пример #13
0
    def post(self, request, *args, **kwargs):
        authorize = AuthorizeEndpoint(request)

        try:
            authorize.validate_params()

            if not request.POST.get('allow'):
                raise AuthorizeError(authorize.params['redirect_uri'],
                                     'access_denied', authorize.grant_type)

            # Save the user consent given to the client.
            authorize.set_client_user_consent()

            uri = authorize.create_response_uri()

            return redirect(uri)

        except (AuthorizeError) as error:
            uri = error.create_uri(authorize.params['redirect_uri'],
                                   authorize.params['state'])

            return redirect(uri)
Пример #14
0
    def get(self, request, *args, **kwargs):

        authorize = AuthorizeEndpoint(request)

        try:
            authorize.validate_params()

            if request.user.is_authenticated():
                # Check if there's a hook setted.
                hook_resp = settings.get('OIDC_AFTER_USERLOGIN_HOOK',
                                         import_str=True)(
                                             request=request,
                                             user=request.user,
                                             client=authorize.client)
                if hook_resp:
                    return hook_resp

                if settings.get('OIDC_SKIP_CONSENT_ALWAYS') and not (authorize.client.client_type == 'public') \
                and not (authorize.params['prompt'] == 'consent'):
                    return redirect(authorize.create_response_uri())

                if settings.get('OIDC_SKIP_CONSENT_ENABLE'):
                    # Check if user previously give consent.
                    if authorize.client_has_user_consent() and not (authorize.client.client_type == 'public') \
                    and not (authorize.params['prompt'] == 'consent'):
                        return redirect(authorize.create_response_uri())

                if authorize.params['prompt'] == 'none':
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'interaction_required',
                                         authorize.grant_type)

                if authorize.params['prompt'] == 'login':
                    return redirect_to_login(request.get_full_path())

                if authorize.params['prompt'] == 'select_account':
                    # TODO: see how we can support multiple accounts for the end-user.
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'account_selection_required',
                                         authorize.grant_type)

                # Generate hidden inputs for the form.
                context = {
                    'params': authorize.params,
                }
                hidden_inputs = render_to_string(
                    'oidc_provider/hidden_inputs.html', context)

                # Remove `openid` from scope list
                # since we don't need to print it.
                if 'openid' in authorize.params['scope']:
                    authorize.params['scope'].remove('openid')

                context = {
                    'client': authorize.client,
                    'hidden_inputs': hidden_inputs,
                    'params': authorize.params,
                    'scopes': authorize.get_scopes_information(),
                }

                return render(request, 'oidc_provider/authorize.html', context)
            else:
                if authorize.params['prompt'] == 'none':
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'login_required',
                                         authorize.grant_type)

                return redirect_to_login(request.get_full_path())

        except (ClientIdError, RedirectUriError) as error:
            context = {
                'error': error.error,
                'description': error.description,
            }

            return render(request, 'oidc_provider/error.html', context)

        except (AuthorizeError) as error:
            uri = error.create_uri(authorize.params['redirect_uri'],
                                   authorize.params['state'])

            return redirect(uri)
    def create_response_uri(self):
        uri = urlsplit(self.params['redirect_uri'])
        query_params = parse_qs(uri.query)
        query_fragment = {}

        try:
            if self.grant_type in ['authorization_code', 'hybrid']:
                code = create_code(
                    user=self.request.user,
                    client=self.client,
                    scope=self.params['scope'],
                    nonce=self.params['nonce'],
                    is_authentication=self.is_authentication,
                    code_challenge=self.params['code_challenge'],
                    code_challenge_method=self.params['code_challenge_method'])
                code.save()

            if self.grant_type == 'authorization_code':
                query_params['code'] = code.code
                query_params['state'] = self.params['state'] if self.params[
                    'state'] else ''
            elif self.grant_type in ['implicit', 'hybrid']:
                token = create_token(user=self.request.user,
                                     client=self.client,
                                     scope=self.params['scope'])

                # Check if response_type must include access_token in the response.
                if (self.params['response_type'] in [
                        'id_token token', 'token', 'code token',
                        'code id_token token'
                ]):
                    query_fragment['access_token'] = token.access_token

                # We don't need id_token if it's an OAuth2 request.
                if self.is_authentication:
                    kwargs = {
                        'token': token,
                        'user': self.request.user,
                        'aud': self.client.client_id,
                        'nonce': self.params['nonce'],
                        'request': self.request,
                        'scope': self.params['scope'],
                    }
                    # Include at_hash when access_token is being returned.
                    if 'access_token' in query_fragment:
                        kwargs['at_hash'] = token.at_hash
                    id_token_dic = create_id_token(**kwargs)

                    # Check if response_type must include id_token in the response.
                    if self.params['response_type'] in [
                            'id_token', 'id_token token', 'code id_token',
                            'code id_token token'
                    ]:
                        query_fragment['id_token'] = encode_id_token(
                            id_token_dic, self.client)
                else:
                    id_token_dic = {}

                # Store the token.
                token.id_token = id_token_dic
                token.save()

                # Code parameter must be present if it's Hybrid Flow.
                if self.grant_type == 'hybrid':
                    query_fragment['code'] = code.code

                query_fragment['token_type'] = 'bearer'

                query_fragment['expires_in'] = settings.get(
                    'OIDC_TOKEN_EXPIRE')

                query_fragment['state'] = self.params['state'] if self.params[
                    'state'] else ''

            if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'):
                # Generate client origin URI from the redirect_uri param.
                redirect_uri_parsed = urlsplit(self.params['redirect_uri'])
                client_origin = '{0}://{1}'.format(redirect_uri_parsed.scheme,
                                                   redirect_uri_parsed.netloc)

                # Create random salt.
                salt = md5(uuid4().hex.encode()).hexdigest()

                # The generation of suitable Session State values is based
                # on a salted cryptographic hash of Client ID, origin URL,
                # and OP browser state.
                session_state = '{client_id} {origin} {browser_state} {salt}'.format(
                    client_id=self.client.client_id,
                    origin=client_origin,
                    browser_state=get_browser_state_or_default(self.request),
                    salt=salt)
                session_state = sha256(
                    session_state.encode('utf-8')).hexdigest()
                session_state += '.' + salt
                if self.grant_type == 'authorization_code':
                    query_params['session_state'] = session_state
                elif self.grant_type in ['implicit', 'hybrid']:
                    query_fragment['session_state'] = session_state

        except Exception as error:
            logger.exception(
                '[Authorize] Error when trying to create response uri: %s',
                error)
            raise AuthorizeError(self.params['redirect_uri'], 'server_error',
                                 self.grant_type)

        uri = uri._replace(query=urlencode(query_params, doseq=True),
                           fragment=uri.fragment +
                           urlencode(query_fragment, doseq=True))

        return urlunsplit(uri)
Пример #16
0
    def get(self, request, *args, **kwargs):
        authorize = self.authorize_endpoint_class(request)

        try:
            authorize.validate_params()

            if get_attr_or_callable(request.user, 'is_authenticated'):
                # Check if there's a hook setted.
                hook_resp = settings.get('OIDC_AFTER_USERLOGIN_HOOK',
                                         import_str=True)(
                                             request=request,
                                             user=request.user,
                                             client=authorize.client)
                if hook_resp:
                    return hook_resp

                if 'login' in authorize.params['prompt']:
                    if 'none' in authorize.params['prompt']:
                        raise AuthorizeError(authorize.params['redirect_uri'],
                                             'login_required',
                                             authorize.grant_type)
                    else:
                        django_user_logout(request)
                        next_page = strip_prompt_login(request.get_full_path())
                        return redirect_to_login(
                            next_page, settings.get('OIDC_LOGIN_URL'))

                if 'select_account' in authorize.params['prompt']:
                    # TODO: see how we can support multiple accounts for the end-user.
                    if 'none' in authorize.params['prompt']:
                        raise AuthorizeError(authorize.params['redirect_uri'],
                                             'account_selection_required',
                                             authorize.grant_type)
                    else:
                        django_user_logout(request)
                        return redirect_to_login(
                            request.get_full_path(),
                            settings.get('OIDC_LOGIN_URL'))

                if {'none', 'consent'}.issubset(authorize.params['prompt']):
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'consent_required',
                                         authorize.grant_type)

                if not authorize.client.require_consent and (
                        'consent' not in authorize.params['prompt']):
                    return redirect(authorize.create_response_uri())

                if authorize.client.reuse_consent:
                    # Check if user previously give consent.
                    if authorize.client_has_user_consent() and (
                            'consent' not in authorize.params['prompt']):
                        return redirect(authorize.create_response_uri())

                if 'none' in authorize.params['prompt']:
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'consent_required',
                                         authorize.grant_type)

                # Generate hidden inputs for the form.
                context = {
                    'params': authorize.params,
                }
                hidden_inputs = render_to_string(
                    'oidc_provider/hidden_inputs.html', context)

                # Remove `openid` from scope list
                # since we don't need to print it.
                if 'openid' in authorize.params['scope']:
                    authorize.params['scope'].remove('openid')

                context = {
                    'client': authorize.client,
                    'hidden_inputs': hidden_inputs,
                    'params': authorize.params,
                    'scopes': authorize.get_scopes_information(),
                }

                return render(request, OIDC_TEMPLATES['authorize'], context)
            else:
                if 'none' in authorize.params['prompt']:
                    raise AuthorizeError(authorize.params['redirect_uri'],
                                         'login_required',
                                         authorize.grant_type)
                if 'login' in authorize.params['prompt']:
                    next_page = strip_prompt_login(request.get_full_path())
                    return redirect_to_login(next_page,
                                             settings.get('OIDC_LOGIN_URL'))

                return redirect_to_login(request.get_full_path(),
                                         settings.get('OIDC_LOGIN_URL'))

        except (ClientIdError, RedirectUriError) as error:
            context = {
                'error': error.error,
                'description': error.description,
            }

            return render(request, OIDC_TEMPLATES['error'], context)

        except AuthorizeError as error:
            uri = error.create_uri(authorize.params['redirect_uri'],
                                   authorize.params['state'])

            return redirect(uri)
Пример #17
0
    def create_response_uri(self):
        uri = urlsplit(self.params.redirect_uri)
        query_params = parse_qs(uri.query)
        query_fragment = parse_qs(uri.fragment)

        try:
            if self.grant_type == 'authorization_code':
                code = create_code(
                    user=self.request.user,
                    client=self.client,
                    scope=self.params.scope,
                    nonce=self.params.nonce,
                    is_authentication=self.is_authentication,
                    code_challenge=self.params.code_challenge,
                    code_challenge_method=self.params.code_challenge_method)

                code.save()

                query_params['code'] = code.code
                query_params['state'] = self.params.state if self.params.state else ''

            elif self.grant_type == 'implicit':
                token = create_token(
                    user=self.request.user,
                    client=self.client,
                    scope=self.params.scope)

                # Check if response_type is an OpenID request with value 'id_token token'
                # or it's an OAuth2 Implicit Flow request.
                if self.params.response_type in ['id_token token', 'token']:
                    query_fragment['access_token'] = token.access_token

                # We don't need id_token if it's an OAuth2 request.
                if self.is_authentication:
                    kwargs = {
                        "user": self.request.user,
                        "aud": self.client.client_id,
                        "nonce": self.params.nonce,
                        "request": self.request
                    }
                    # Include at_hash when access_token is being returned.
                    if 'access_token' in query_fragment:
                        kwargs['at_hash'] = token.at_hash
                    id_token_dic = create_id_token(**kwargs)
                    query_fragment['id_token'] = encode_id_token(id_token_dic, self.client)
                    token.id_token = id_token_dic
                else:
                    id_token_dic = {}

                # Store the token.
                token.id_token = id_token_dic
                token.save()

                query_fragment['token_type'] = 'bearer'
                # TODO: Create setting 'OIDC_TOKEN_EXPIRE'.
                query_fragment['expires_in'] = 60 * 10

                query_fragment['state'] = self.params.state if self.params.state else ''

        except Exception as error:
            logger.debug('[Authorize] Error when trying to create response uri: %s', error)
            raise AuthorizeError(
                self.params.redirect_uri,
                'server_error',
                self.grant_type)

        uri = uri._replace(query=urlencode(query_params, doseq=True))
        uri = uri._replace(fragment=urlencode(query_fragment, doseq=True))

        return urlunsplit(uri)
Пример #18
0
    def create_response_uri(self):
        uri = urlsplit(self.params['redirect_uri'])
        query_params = parse_qs(uri.query)
        query_fragment = parse_qs(uri.fragment)

        try:
            if self.grant_type in ['authorization_code', 'hybrid']:
                code = create_code(
                    user=self.request.user,
                    client=self.client,
                    scope=self.params['scope'],
                    nonce=self.params['nonce'],
                    is_authentication=self.is_authentication,
                    code_challenge=self.params['code_challenge'],
                    code_challenge_method=self.params['code_challenge_method'])
                code.save()

            if self.grant_type == 'authorization_code':
                query_params['code'] = code.code
                query_params['state'] = self.params['state'] if self.params['state'] else ''
            elif self.grant_type in ['implicit', 'hybrid']:
                token = create_token(
                    user=self.request.user,
                    client=self.client,
                    scope=self.params['scope'])

                # Check if response_type must include access_token in the response.
                if self.params['response_type'] in ['id_token token', 'token', 'code token', 'code id_token token']:
                    query_fragment['access_token'] = token.access_token

                # We don't need id_token if it's an OAuth2 request.
                if self.is_authentication:
                    kwargs = {
                        'user': self.request.user,
                        'aud': self.client.client_id,
                        'nonce': self.params['nonce'],
                        'request': self.request,
                        'scope': self.params['scope'],
                    }
                    # Include at_hash when access_token is being returned.
                    if 'access_token' in query_fragment:
                        kwargs['at_hash'] = token.at_hash
                    id_token_dic = create_id_token(**kwargs)

                    # Check if response_type must include id_token in the response.
                    if self.params['response_type'] in ['id_token', 'id_token token', 'code id_token', 'code id_token token']:
                        query_fragment['id_token'] = encode_id_token(id_token_dic, self.client)
                else:
                    id_token_dic = {}

                # Store the token.
                token.id_token = id_token_dic
                token.save()

                # Code parameter must be present if it's Hybrid Flow.
                if self.grant_type == 'hybrid':
                    query_fragment['code'] = code.code

                query_fragment['token_type'] = 'bearer'

                query_fragment['expires_in'] = settings.get('OIDC_TOKEN_EXPIRE')

                query_fragment['state'] = self.params['state'] if self.params['state'] else ''

        except Exception as error:
            logger.debug('[Authorize] Error when trying to create response uri: %s', error)
            raise AuthorizeError(self.params['redirect_uri'], 'server_error', self.grant_type)

        uri = uri._replace(query=urlencode(query_params, doseq=True))
        uri = uri._replace(fragment=urlencode(query_fragment, doseq=True))

        return urlunsplit(uri)