def _update_user_identities(
            self, keycloak_id: UUID,
            federated_identities: List[KeycloakFederatedIdentity]):
        keycloak_identities = {
            i['identityProvider']: i
            for i in self._get_user_identities(keycloak_id)
        }
        for identity in federated_identities:
            if identity.provider_name in keycloak_identities:
                response = requests.post(
                    f'{self._get_identities_url(user_id=keycloak_id)}/'
                    f'{identity.provider_name}',
                    data=json.dumps({
                        'identityProvider': identity.provider_name,
                        'userId': identity.user_id,
                        'userName': identity.user_name,
                    }),
                    headers={
                        'Content-Type': 'application/json',
                        'Authorization': self._get_authorization_header(),
                    })

                if not response.ok:
                    raise KeycloakApiClientException(
                        'Error while creating identity for user '
                        f'{keycloak_id} (msg: {response.json()})')
    def _get_user_identities(self, keycloak_id: UUID) -> List[dict]:
        response = requests.get(
            self._get_identities_url(user_id=keycloak_id),
            headers={'Authorization': self._get_authorization_header()})

        if not response.ok:
            raise KeycloakApiClientException(
                'Error while retrieving identities of user '
                f'{keycloak_id} (msg: {response.json()})')

        return response.json()
    def update_user(self, write_keycloak_user: WriteKeycloakUser):
        response = requests.put(
            f'{self._get_users_url()}/{write_keycloak_user.keycloak_id}',
            data=json.dumps(
                self._get_user_endpoint_schema_data(
                    write_keycloak_user=write_keycloak_user)),
            headers={
                'Content-Type': 'application/json',
                'Authorization': self._get_authorization_header(),
            })

        if not response.ok:
            raise KeycloakApiClientException(
                f'Error while updating user (msg: {response.json()})')

        if write_keycloak_user.federated_identities:
            self._update_user_identities(
                keycloak_id=write_keycloak_user.keycloak_id,
                federated_identities=write_keycloak_user.federated_identities)
    def get_keycloak_user_by_id(
            self,
            keycloak_id: Optional[UUID] = None) -> Optional[ReadKeycloakUser]:
        response = requests.get(
            f'{self._get_users_url()}/{keycloak_id}',
            headers={'Authorization': self._get_authorization_header()})

        if response.status_code == HTTPStatus.NOT_FOUND:
            return None

        if not response.ok:
            raise KeycloakApiClientException(
                f'Error while retrieving user with id {keycloak_id} '
                f'(msg: {response.json()})')

        if not response.json():
            return None

        return read_keycloak_user_factory(user_endpoint_data=response.json())
    def get_keycloak_user_by_email(
        self,
        email: Optional[str] = None,
    ) -> Optional[ReadKeycloakUser]:
        response = requests.get(
            f'{self._get_users_url()}?email={parse.quote(email)}',
            headers={'Authorization': self._get_authorization_header()})

        if not response.ok:
            raise KeycloakApiClientException(
                f'Error while retrieving user with email {email} '
                f'(msg: {response.json()})')

        if len(response.json()) == 0:
            return None

        try:
            return read_keycloak_user_factory(user_endpoint_data=next(
                user for user in response.json() if user['email'] == email))
        except StopIteration:
            return None
    def search_users(self,
                     query: str,
                     limit: int = 100,
                     offset: int = 0) -> List[ReadKeycloakUser]:
        response = requests.get(
            self._get_users_url(),
            params={
                'search': query,
                'max': limit,
                'first': offset
            },
            headers={'Authorization': self._get_authorization_header()})

        if not response.ok:
            raise KeycloakApiClientException(
                f'Error while retrieving users with query {query} '
                f'(msg: {response.json()})')

        return [
            read_keycloak_user_factory(user_endpoint_data=user_data)
            for user_data in response.json()
        ]
    def _get_api_admin_oidc_token(self) -> str:
        if self.admin_user_access_token:
            return self.admin_user_access_token

        response = requests.post(
            self._get_token_url(),
            data={
                'grant_type': 'password',
                'username': self.admin_username,
                'password': self.admin_password,
                'client_id': self.admin_client_id,
                'client_secret': self.admin_client_secret
            },
            headers={'Content-Type': 'application/x-www-form-urlencoded'})

        if not response.ok:
            raise KeycloakApiClientException(
                'Error while obtaining api-admin access_token '
                f'(msg: {response.json()})')

        self.admin_user_access_token = response.json()['access_token']

        return self.admin_user_access_token
    def get_user_tokens(self, keycloak_id: UUID) -> KeycloakTokens:
        response = requests.post(
            self._get_token_url(),
            data={
                'audience': self.token_exchange_target_client_id,
                'grant_type': 'urn:ietf:params:oauth:grant-type'
                ':token-exchange',
                'requested_subject': str(keycloak_id),
                'subject_token': self._get_api_admin_oidc_token(),
                'client_id': self.token_exchange_target_client_id,
                'client_secret': self.admin_client_secret,
            },
            headers={'Content-Type': 'application/x-www-form-urlencoded'})

        if not response.ok:
            raise KeycloakApiClientException(
                'Error while obtaining user tokens '
                f'{keycloak_id} (msg: {response.json()})')

        data = response.json()

        return KeycloakTokens(access_token=data['access_token'],
                              refresh_token=data['refresh_token'])
    def register_user(self, write_keycloak_user: WriteKeycloakUser) -> UUID:
        response = requests.post(
            self._get_users_url(),
            data=json.dumps(
                self._get_user_endpoint_schema_data(
                    write_keycloak_user=write_keycloak_user)),
            headers={
                'Content-Type': 'application/json',
                'Authorization': self._get_authorization_header(),
            })

        if not response.ok:
            raise KeycloakApiClientException(
                f'Error while creating user (msg: {response.json()})')

        keycloak_id = response.headers['Location'].split('/')[-1]

        if write_keycloak_user.federated_identities:
            self._update_user_identities(
                keycloak_id=UUID(keycloak_id),
                federated_identities=write_keycloak_user.federated_identities)

        return UUID(keycloak_id)