forked from EVOgeek/Vmeda.Online
		
	MOBILE-4028 logout: Refactor logout process
Now it uses a logout page so Angular guards are triggered before doing the logout process.
This commit is contained in:
		
							parent
							
								
									f39d4a9240
								
							
						
					
					
						commit
						feff5a87f8
					
				@ -216,16 +216,7 @@ export class CoreContentLinksDelegateService {
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            // Site is logged out, authenticate first before treating the URL.
 | 
			
		||||
                            const willReload = await CoreSites.logoutForRedirect(siteId, {
 | 
			
		||||
                                urlToOpen: url,
 | 
			
		||||
                            });
 | 
			
		||||
 | 
			
		||||
                            if (!willReload) {
 | 
			
		||||
                                // Load the site with the redirect data.
 | 
			
		||||
                                await CoreSites.loadSite(siteId, {
 | 
			
		||||
                                    urlToOpen: url,
 | 
			
		||||
                                });
 | 
			
		||||
                            }
 | 
			
		||||
                            await CoreSites.logout({ urlToOpen: url, siteId });
 | 
			
		||||
                        };
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,10 @@ const appRoutes: Routes = [
 | 
			
		||||
        loadChildren: () => import('./login-lazy.module'),
 | 
			
		||||
        canActivate: [redirectGuard],
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        path: 'logout',
 | 
			
		||||
        loadComponent: () => import('@features/login/pages/logout/logout'),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								src/core/features/login/pages/logout/logout.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/core/features/login/pages/logout/logout.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <core-loading />
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										104
									
								
								src/core/features/login/pages/logout/logout.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/core/features/login/pages/logout/logout.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,104 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { CoreNavigationOptions, CoreNavigator, CoreRedirectPayload } from '@services/navigator';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
 | 
			
		||||
import { CoreRedirects } from '@singletons/redirects';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that logs the user out.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-core-login-logout',
 | 
			
		||||
    templateUrl: 'logout.html',
 | 
			
		||||
    standalone: true,
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export default class CoreLoginLogoutPage implements OnInit {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        const siteId = CoreNavigator.getRouteParam('siteId') ?? CoreConstants.NO_SITE_ID;
 | 
			
		||||
        const logoutOptions = {
 | 
			
		||||
            forceLogout: CoreNavigator.getRouteBooleanParam('forceLogout'),
 | 
			
		||||
            removeAccount: CoreNavigator.getRouteBooleanParam('removeAccount') ?? !!CoreConstants.CONFIG.removeaccountonlogout,
 | 
			
		||||
        };
 | 
			
		||||
        const redirectData = {
 | 
			
		||||
            redirectPath: CoreNavigator.getRouteParam('redirectPath'),
 | 
			
		||||
            redirectOptions: CoreNavigator.getRouteParam<CoreNavigationOptions>('redirectOptions'),
 | 
			
		||||
            urlToOpen: CoreNavigator.getRouteParam('urlToOpen'),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if (!CoreSites.isLoggedIn()) {
 | 
			
		||||
            // This page shouldn't open if user isn't logged in, but if that happens just navigate to the right page.
 | 
			
		||||
            await this.navigateAfterLogout(siteId, redirectData);
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const shouldReload = CoreSitePlugins.hasSitePluginsLoaded;
 | 
			
		||||
        if (shouldReload && (siteId !== CoreConstants.NO_SITE_ID || redirectData.redirectPath || redirectData.urlToOpen)) {
 | 
			
		||||
            // The app will reload and we need to open a page that isn't the default page. Store the redirect first.
 | 
			
		||||
            CoreRedirects.storeRedirect(siteId, redirectData);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreSites.internalLogout(logoutOptions);
 | 
			
		||||
 | 
			
		||||
        if (shouldReload) {
 | 
			
		||||
            // We need to reload the app to unload all the plugins. Leave the logout page first.
 | 
			
		||||
            await CoreNavigator.navigate('/login', { reset: true });
 | 
			
		||||
 | 
			
		||||
            window.location.reload();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.navigateAfterLogout(siteId, redirectData);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Navigate to the right page after logout is done.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID to load.
 | 
			
		||||
     * @param redirectData Redirect data.
 | 
			
		||||
     */
 | 
			
		||||
    protected async navigateAfterLogout(siteId: string, redirectData: CoreRedirectPayload): Promise<void> {
 | 
			
		||||
        if (siteId === CoreConstants.NO_SITE_ID) {
 | 
			
		||||
            // No site to load now, just navigate.
 | 
			
		||||
            await CoreNavigator.navigate(redirectData.redirectPath ?? '/login/sites', {
 | 
			
		||||
                ...redirectData.redirectOptions,
 | 
			
		||||
                reset: true,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Load the site and navigate.
 | 
			
		||||
        const loggedIn = await CoreSites.loadSite(siteId, redirectData);
 | 
			
		||||
        if (!loggedIn) {
 | 
			
		||||
            return; // Session expired.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreNavigator.navigateToSiteHome({ params: redirectData, preferCurrentTab: false, siteId });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -412,22 +412,19 @@ export class CoreLoginHelperProvider {
 | 
			
		||||
     * @returns Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async goToAddSite(setRoot = false, showKeyboard = false): Promise<void> {
 | 
			
		||||
        let path = '/login/sites';
 | 
			
		||||
        let params: Params = { openAddSite: true , showKeyboard };
 | 
			
		||||
 | 
			
		||||
        if (CoreSites.isLoggedIn()) {
 | 
			
		||||
            const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
 | 
			
		||||
                redirectPath: path,
 | 
			
		||||
                redirectOptions: { params },
 | 
			
		||||
            // Logout first.
 | 
			
		||||
            await CoreSites.logout({
 | 
			
		||||
                siteId: CoreConstants.NO_SITE_ID,
 | 
			
		||||
                redirectPath: '/login/sites',
 | 
			
		||||
                redirectOptions: { params: { openAddSite: true , showKeyboard } },
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (willReload) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const [path, params] = await this.getAddSiteRouteInfo(showKeyboard);
 | 
			
		||||
 | 
			
		||||
        await CoreNavigator.navigate(path, { params, reset: setRoot });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { CoreSite } from '@classes/sites/site';
 | 
			
		||||
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
 | 
			
		||||
import { CoreFilter } from '@features/filter/services/filter';
 | 
			
		||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
 | 
			
		||||
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
 | 
			
		||||
import { CoreUserSupport } from '@features/user/services/support';
 | 
			
		||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
 | 
			
		||||
@ -33,8 +32,9 @@ import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CorePromiseUtils } from '@singletons/promise-utils';
 | 
			
		||||
import { ModalController, Translate } from '@singletons';
 | 
			
		||||
import { ModalController } from '@singletons';
 | 
			
		||||
import { Subscription } from 'rxjs';
 | 
			
		||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to display a user menu.
 | 
			
		||||
@ -208,12 +208,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
 | 
			
		||||
     * @param event Click event
 | 
			
		||||
     */
 | 
			
		||||
    async logout(event: Event): Promise<void> {
 | 
			
		||||
        if (CoreNavigator.currentRouteCanBlockLeave()) {
 | 
			
		||||
            await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.removeAccountOnLogout) {
 | 
			
		||||
            // Ask confirm.
 | 
			
		||||
            const siteName = this.siteName ?
 | 
			
		||||
@ -242,12 +236,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
 | 
			
		||||
     * @param event Click event
 | 
			
		||||
     */
 | 
			
		||||
    async switchAccounts(event: Event): Promise<void> {
 | 
			
		||||
        if (CoreNavigator.currentRouteCanBlockLeave()) {
 | 
			
		||||
            await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const thisModal = await ModalController.getTop();
 | 
			
		||||
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
 | 
			
		||||
@ -280,9 +280,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
 | 
			
		||||
     * @returns Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async cancel(): Promise<void> {
 | 
			
		||||
        await CorePromiseUtils.ignoreErrors(CoreSites.logout());
 | 
			
		||||
 | 
			
		||||
        await CoreNavigator.navigate('/login/sites', { reset: true });
 | 
			
		||||
        await CoreSites.logout();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@ describe('Site Home link handlers', () => {
 | 
			
		||||
            isStoredRootURL: () => Promise.resolve({ siteIds: [siteId] }),
 | 
			
		||||
            getSite: () => Promise.resolve(new CoreSite(siteId, siteUrl, '')),
 | 
			
		||||
            getSiteIdsFromUrl: () => Promise.resolve([siteId]),
 | 
			
		||||
            getCurrentSiteId: () => siteId,
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        mockSingleton(CoreLoginHelper, { getAvailableSites: async () => [{ url: siteUrl, name: 'Example Campus' }] });
 | 
			
		||||
 | 
			
		||||
@ -220,14 +220,13 @@ export class CoreNavigatorService {
 | 
			
		||||
 | 
			
		||||
        // If we are logged into a different site, log out first.
 | 
			
		||||
        if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
 | 
			
		||||
            const willReload = await CoreSites.logoutForRedirect(siteId, {
 | 
			
		||||
            await CoreSites.logout({
 | 
			
		||||
                redirectPath: path,
 | 
			
		||||
                redirectOptions: options || {},
 | 
			
		||||
                siteId,
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (willReload) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If the path doesn't belong to a site, call standard navigation.
 | 
			
		||||
 | 
			
		||||
@ -141,14 +141,6 @@ export class CoreSitesProvider {
 | 
			
		||||
 | 
			
		||||
            // Remove version classes from body.
 | 
			
		||||
            CoreHTMLClasses.removeSiteClasses();
 | 
			
		||||
 | 
			
		||||
            // Go to sites page when user is logged out.
 | 
			
		||||
            await CoreNavigator.navigate('/login/sites', { reset: true });
 | 
			
		||||
 | 
			
		||||
            if (CoreSitePlugins.hasSitePluginsLoaded) {
 | 
			
		||||
                // Temporary fix. Reload the page to unload all plugins.
 | 
			
		||||
                window.location.reload();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        CoreEvents.on(CoreEvents.LOGIN, async (data) => {
 | 
			
		||||
@ -964,7 +956,7 @@ export class CoreSitesProvider {
 | 
			
		||||
            promise.finally(() => {
 | 
			
		||||
                if (siteId) {
 | 
			
		||||
                    // Logout the currentSite and expire the token.
 | 
			
		||||
                    this.logout();
 | 
			
		||||
                    this.internalLogout();
 | 
			
		||||
                    this.setSiteLoggedOut(siteId);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
@ -1123,7 +1115,7 @@ export class CoreSitesProvider {
 | 
			
		||||
        this.logger.debug(`Delete site ${siteId}`);
 | 
			
		||||
 | 
			
		||||
        if (this.currentSite !== undefined && this.currentSite.id == siteId) {
 | 
			
		||||
            this.logout();
 | 
			
		||||
            this.internalLogout();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const site = await this.getSite(siteId);
 | 
			
		||||
@ -1457,10 +1449,23 @@ export class CoreSitesProvider {
 | 
			
		||||
    /**
 | 
			
		||||
     * Logout the user.
 | 
			
		||||
     *
 | 
			
		||||
     * @param options Logout options.
 | 
			
		||||
     * @returns Promise resolved when the user is logged out.
 | 
			
		||||
     * @param options Options.
 | 
			
		||||
     */
 | 
			
		||||
    async logout(options: CoreSitesLogoutOptions = {}): Promise<void> {
 | 
			
		||||
        await CoreNavigator.navigate('/logout', {
 | 
			
		||||
            params: { ...options },
 | 
			
		||||
            reset: true,
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Logout the user.
 | 
			
		||||
     * This function is for internal usage, please use CoreSites.logout instead. The reason this function is public is because
 | 
			
		||||
     * it's called from the CoreLoginLogoutPage page.
 | 
			
		||||
     *
 | 
			
		||||
     * @param options Logout options.
 | 
			
		||||
     */
 | 
			
		||||
    async internalLogout(options: InternalLogoutOptions = {}): Promise<void> {
 | 
			
		||||
        if (!this.currentSite) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
@ -1494,6 +1499,7 @@ export class CoreSitesProvider {
 | 
			
		||||
     * @param siteId Site that will be opened after logout.
 | 
			
		||||
     * @param redirectData Page/url to open after logout.
 | 
			
		||||
     * @returns Promise resolved with boolean: true if app will be reloaded after logout.
 | 
			
		||||
     * @deprecated since 5.0. Use CoreSites.logout instead, it automatically handles redirects.
 | 
			
		||||
     */
 | 
			
		||||
    async logoutForRedirect(siteId: string, redirectData: CoreRedirectPayload): Promise<boolean> {
 | 
			
		||||
        if (!this.currentSite) {
 | 
			
		||||
@ -1505,7 +1511,7 @@ export class CoreSitesProvider {
 | 
			
		||||
            CoreRedirects.storeRedirect(siteId, redirectData);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.logout();
 | 
			
		||||
        await this.internalLogout();
 | 
			
		||||
 | 
			
		||||
        return CoreSitePlugins.hasSitePluginsLoaded;
 | 
			
		||||
    }
 | 
			
		||||
@ -2480,7 +2486,14 @@ export type CoreSitesLoginTokenResponse = {
 | 
			
		||||
/**
 | 
			
		||||
 * Options for logout.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreSitesLogoutOptions = {
 | 
			
		||||
export type CoreSitesLogoutOptions = CoreRedirectPayload & InternalLogoutOptions & {
 | 
			
		||||
    siteId?: string; // Site ID to load after logout.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Options for internal logout.
 | 
			
		||||
 */
 | 
			
		||||
type InternalLogoutOptions = {
 | 
			
		||||
    forceLogout?: boolean; // If true, site will be marked as logged out, no matter the value tool_mobile_forcelogout.
 | 
			
		||||
    removeAccount?: boolean; // If true, site will be removed too after logout.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,6 @@ import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreLang, CoreLangProvider } from '@services/lang';
 | 
			
		||||
 | 
			
		||||
import { mock, mockSingleton } from '@/testing/utils';
 | 
			
		||||
import { CoreNavigator, CoreNavigatorService } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { Http } from '@singletons';
 | 
			
		||||
import { of } from 'rxjs';
 | 
			
		||||
@ -34,13 +33,10 @@ describe('CoreSitesProvider', () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('cleans up on logout', async () => {
 | 
			
		||||
        const navigator: CoreNavigatorService = mockSingleton(CoreNavigator, ['navigate']);
 | 
			
		||||
 | 
			
		||||
        CoreSites.initialize();
 | 
			
		||||
        CoreEvents.trigger(CoreEvents.LOGOUT);
 | 
			
		||||
 | 
			
		||||
        expect(langProvider.clearCustomStrings).toHaveBeenCalled();
 | 
			
		||||
        expect(navigator.navigate).toHaveBeenCalledWith('/login/sites', { reset: true });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('adds ionic platform and theme classes', async () => {
 | 
			
		||||
 | 
			
		||||
@ -432,14 +432,13 @@ export class CoreCustomURLSchemesProvider {
 | 
			
		||||
            // Ask the user before changing site.
 | 
			
		||||
            await CoreDomUtils.showConfirm(Translate.instant('core.contentlinks.confirmurlothersite'));
 | 
			
		||||
 | 
			
		||||
            const willReload = await CoreSites.logoutForRedirect(CoreConstants.NO_SITE_ID, {
 | 
			
		||||
            await CoreSites.logout({
 | 
			
		||||
                siteId: CoreConstants.NO_SITE_ID,
 | 
			
		||||
                redirectPath: '/login/credentials',
 | 
			
		||||
                redirectOptions: { params: pageParams },
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (willReload) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await CoreNavigator.navigateToLoginCredentials(pageParams);
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,10 @@ This file describes API changes in the Moodle App that affect site plugins, info
 | 
			
		||||
 | 
			
		||||
For more information about upgrading, read the official documentation: https://moodledev.io/general/app/upgrading/
 | 
			
		||||
 | 
			
		||||
=== 5.0.0 ===
 | 
			
		||||
 | 
			
		||||
 - The logout process has been refactored, now it uses a logout page to trigger Angular guards. CoreSites.logout now uses this process, and CoreSites.logoutForRedirect is deprecated and shouldn't be used anymore.
 | 
			
		||||
 | 
			
		||||
=== 4.5.0 ===
 | 
			
		||||
 | 
			
		||||
 - Ionic has been upgraded to major version 8. See breaking changes and upgrade guide here: https://ionicframework.com/docs/updating/8-0
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user