From 19caca91e98e41187f76c9925c31b8fba33c2902 Mon Sep 17 00:00:00 2001
From: andreykproductengine <andreykproductengine@lindenlab.com>
Date: Tue, 7 Jan 2020 00:52:04 +0200
Subject: [PATCH] SL-12486 Better data deletion

---
 indra/newview/llappviewer.cpp                 |  15 +-
 indra/newview/llappviewer.h                   |   4 +-
 indra/newview/llfloaterforgetuser.cpp         | 231 ++++++++++++++----
 indra/newview/llfloaterforgetuser.h           |   9 +-
 indra/newview/llsecapi.h                      |   4 +
 indra/newview/llsechandler_basic.cpp          |  13 +
 indra/newview/llsechandler_basic.h            |   4 +
 indra/newview/llstartup.cpp                   |   9 +
 indra/newview/llstartup.h                     |   1 +
 .../skins/default/xui/en/notifications.xml    |  12 +
 10 files changed, 245 insertions(+), 57 deletions(-)

diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index bbb726ddb0e..b232a8c3bbf 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -671,7 +671,8 @@ LLAppViewer::LLAppViewer()
 	mReportedCrash(false),
 	mNumSessions(0),
 	mPurgeCache(false),
-	mPurgeOnExit(false),
+	mPurgeCacheOnExit(false),
+	mPurgeUserDataOnExit(false),
 	mSecondInstance(false),
 	mSavedFinalSnapshot(false),
 	mSavePerAccountSettings(false),		// don't save settings on logout unless login succeeded.
@@ -1949,7 +1950,7 @@ bool LLAppViewer::cleanup()
 		LLConversationLog::instance().cache();
 	}
 
-	if (mPurgeOnExit)
+	if (mPurgeCacheOnExit)
 	{
 		LL_INFOS() << "Purging all cache files on exit" << LL_ENDL;
 		gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*");
@@ -1990,6 +1991,14 @@ bool LLAppViewer::cleanup()
 		}
 	}
 
+    if (mPurgeUserDataOnExit)
+    {
+        // Ideally we should not save anything from this session since it is going to be purged now,
+        // but this is a very 'rare' case (user deleting himself), not worth overcomplicating 'save&cleanup' code
+        std::string user_path = gDirUtilp->getOSUserAppDir() + gDirUtilp->getDirDelimiter() + LLStartUp::getUserId();
+        gDirUtilp->deleteDirAndContents(user_path);
+    }
+
 	// Delete workers first
 	// shotdown all worker threads before deleting them in case of co-dependencies
 	mAppCoreHttp.requestStop();
@@ -4461,7 +4470,7 @@ void LLAppViewer::badNetworkHandler()
 	// Flush all of our caches on exit in the case of disconnect due to
 	// invalid packets.
 
-	mPurgeOnExit = TRUE;
+	mPurgeCacheOnExit = TRUE;
 
 	std::ostringstream message;
 	message <<
diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h
index 788fe6a19b0..1298ba51e24 100644
--- a/indra/newview/llappviewer.h
+++ b/indra/newview/llappviewer.h
@@ -190,6 +190,7 @@ class LLAppViewer : public LLApp
 
 	void addOnIdleCallback(const boost::function<void()>& cb); // add a callback to fire (once) when idle
 
+	void purgeUserDataOnExit() { mPurgeUserDataOnExit = true; }
 	void purgeCache(); // Clear the local cache. 
 	void purgeCacheImmediate(); //clear local cache immediately.
 	S32  updateTextureThreads(F32 max_time);
@@ -281,7 +282,8 @@ class LLAppViewer : public LLApp
 
 	std::string mSerialNumber;
 	bool mPurgeCache;
-    bool mPurgeOnExit;
+	bool mPurgeCacheOnExit;
+	bool mPurgeUserDataOnExit;
 	LLViewerJoystick* joystick;
 
 	bool mSavedFinalSnapshot;
diff --git a/indra/newview/llfloaterforgetuser.cpp b/indra/newview/llfloaterforgetuser.cpp
index 363951041af..6271ccfa180 100644
--- a/indra/newview/llfloaterforgetuser.cpp
+++ b/indra/newview/llfloaterforgetuser.cpp
@@ -29,11 +29,15 @@
 
 #include "llfloaterforgetuser.h"
 
+#include "llappviewer.h"
 #include "llcheckboxctrl.h"
 #include "llfavoritesbar.h"
+#include "llnotificationsutil.h"
 #include "llpanellogin.h"        // for helper function getUserName() and to repopulate list if nessesary
 #include "llscrolllistctrl.h"
 #include "llsecapi.h"
+#include "llstartup.h"
+#include "llviewercontrol.h"
 #include "llviewernetwork.h"
 
 
@@ -54,55 +58,65 @@ LLFloaterForgetUser::~LLFloaterForgetUser()
 
 BOOL LLFloaterForgetUser::postBuild()
 {
-    // Note, storage works per grid, whatever is selected currently in login screen or logged in.
-    // Since login screen can change grid, store the value.
-    mGrid = LLGridManager::getInstance()->getGrid();
+    mScrollList = getChild<LLScrollListCtrl>("user_list");
 
-    LLScrollListCtrl *scroll_list = getChild<LLScrollListCtrl>("user_list");
-    if (gSecAPIHandler->hasCredentialMap("login_list", mGrid))
-    {
-        LLSecAPIHandler::credential_map_t credencials;
-        gSecAPIHandler->loadCredentialMap("login_list", mGrid, credencials);
 
-        LLSecAPIHandler::credential_map_t::iterator cr_iter = credencials.begin();
-        LLSecAPIHandler::credential_map_t::iterator cr_end = credencials.end();
-        while (cr_iter != cr_end)
+    bool show_grid_marks = gSavedSettings.getBOOL("ForceShowGrid");
+    show_grid_marks |= !LLGridManager::getInstance()->isInProductionGrid();
+
+    std::map<std::string, std::string> known_grids = LLGridManager::getInstance()->getKnownGrids();
+
+    if (!show_grid_marks)
+    {
+        // Figure out if there are records for more than one grid in storage
+        for (std::map<std::string, std::string>::iterator grid_iter = known_grids.begin();
+            grid_iter != known_grids.end();
+            grid_iter++)
         {
-            if (cr_iter->second.notNull()) // basic safety
+            if (!grid_iter->first.empty()
+                && grid_iter->first != MAINGRID) // a workaround since 'mIsInProductionGrid' might not be set
             {
-                LLScrollListItem::Params item_params;
-                item_params.value(cr_iter->first);
-                item_params.columns.add()
-                    .value(LLPanelLogin::getUserName(cr_iter->second))
-                    .column("user")
-                    .font(LLFontGL::getFontSansSerifSmall());
-                scroll_list->addRow(item_params, ADD_BOTTOM);
+                if (!gSecAPIHandler->emptyCredentialMap("login_list", grid_iter->first))
+                {
+                    show_grid_marks = true;
+                    break;
+                }
+
+                // "Legacy" viewer support
+                LLPointer<LLCredential> cred = gSecAPIHandler->loadCredential(grid_iter->first);
+                if (cred.notNull())
+                {
+                    const LLSD &ident = cred->getIdentifier();
+                    if (ident.isMap() && ident.has("type"))
+                    {
+                        show_grid_marks = true;
+                        break;
+                    }
+                }
             }
-            cr_iter++;
         }
-        scroll_list->selectFirstItem();
+    }
+
+    if (!show_grid_marks)
+    {
+        // just load maingrid
+        loadGridToList(MAINGRID, false);
     }
     else
     {
-        LLPointer<LLCredential> cred = gSecAPIHandler->loadCredential(mGrid);
-        if (cred.notNull())
+        for (std::map<std::string, std::string>::iterator grid_iter = known_grids.begin();
+            grid_iter != known_grids.end();
+            grid_iter++)
         {
-            const LLSD &ident = cred->getIdentifier();
-            if (ident.isMap() && ident.has("type"))
+            if (!grid_iter->first.empty())
             {
-                LLScrollListItem::Params item_params;
-                item_params.value(cred->userID());
-                item_params.columns.add()
-                    .value(LLPanelLogin::getUserName(cred))
-                    .column("user")
-                    .font(LLFontGL::getFontSansSerifSmall());
-                scroll_list->addRow(item_params, ADD_BOTTOM);
-                scroll_list->selectFirstItem();
+                loadGridToList(grid_iter->first, true);
             }
         }
     }
 
-    bool enable_button = scroll_list->getFirstSelectedIndex() != -1;
+    mScrollList->selectFirstItem();
+    bool enable_button = mScrollList->getFirstSelectedIndex() != -1;
     LLCheckBoxCtrl *chk_box = getChild<LLCheckBoxCtrl>("delete_data");
     chk_box->setEnabled(enable_button);
     chk_box->set(FALSE);
@@ -117,43 +131,156 @@ void LLFloaterForgetUser::onForgetClicked()
 {
     mLoginPanelDirty = true;
     LLScrollListCtrl *scroll_list = getChild<LLScrollListCtrl>("user_list");
-    std::string user_key = scroll_list->getSelectedValue();
+    LLCheckBoxCtrl *chk_box = getChild<LLCheckBoxCtrl>("delete_data");
+    BOOL delete_data = chk_box->getValue();
+    LLSD user_data = scroll_list->getSelectedValue();
+    const std::string user_id = user_data["user_id"];
+    const std::string grid = user_data["grid"];
+    const std::string user_name = user_data["label"]; // for favorites
 
-    // remove creds
-    gSecAPIHandler->removeFromCredentialMap("login_list", mGrid, user_key);
+    if (delete_data && user_id == LLStartUp::getUserId() && grid == LLGridManager::getInstance()->getGrid())
+    {
+        // we can't delete data for user that is currently logged in
+        LLNotificationsUtil::add("LoginCantRemoveCurUsername", LLSD(), LLSD(), boost::bind(onConfirmLogout, _1, _2, user_name));
+        return;
+    }
+
+    // key is used for name of user's folder and in credencials
+    // user_name is edentical to favorite's username
+    forgetUser(user_id, user_name, grid, delete_data);
 
-    LLPointer<LLCredential> cred = gSecAPIHandler->loadCredential(mGrid);
-    if (cred.notNull() && cred->userID() == user_key)
+    // Update UI
+    scroll_list->deleteSelectedItems();
+    scroll_list->selectFirstItem();
+    if (scroll_list->getFirstSelectedIndex() == -1)
+    {
+        LLButton *button = getChild<LLButton>("forget");
+        button->setEnabled(false);
+        chk_box->setEnabled(false);
+    }
+}
+
+//static
+void LLFloaterForgetUser::forgetUser(const std::string &userid, const std::string &fav_id, const std::string &grid, bool delete_data)
+{
+    // Remove creds
+    gSecAPIHandler->removeFromCredentialMap("login_list", grid, userid);
+
+    LLPointer<LLCredential> cred = gSecAPIHandler->loadCredential(grid);
+    if (cred.notNull() && cred->userID() == userid)
     {
         gSecAPIHandler->deleteCredential(cred);
     }
 
     // Clean data
-    LLCheckBoxCtrl *chk_box = getChild<LLCheckBoxCtrl>("delete_data");
-    BOOL delete_data = chk_box->getValue();
     if (delete_data)
     {
-        // key is edentical to one we use for name of user's folder
-        std::string user_path = gDirUtilp->getOSUserAppDir() + gDirUtilp->getDirDelimiter() + user_key;
+        std::string user_path = gDirUtilp->getOSUserAppDir() + gDirUtilp->getDirDelimiter() + userid;
         gDirUtilp->deleteDirAndContents(user_path);
 
-        // Clean favorites, label is edentical to username
-        LLFavoritesOrderStorage::removeFavoritesRecordOfUser(scroll_list->getSelectedItemLabel(), mGrid);
+        // Clean favorites
+        LLFavoritesOrderStorage::removeFavoritesRecordOfUser(fav_id, grid);
 
         // Note: we do not clean user-related files from cache because there are id dependent (inventory)
         // files and cache has separate cleaning mechanism either way.
         // Also this only cleans user from current grid, not all of them.
     }
+}
 
+// static 
+bool LLFloaterForgetUser::onConfirmLogout(const LLSD& notification, const LLSD& response, const std::string &fav_id)
+{
+    S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+    if (option == 0)
+    {
+        // Remove creds
+        gSecAPIHandler->removeFromCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), LLStartUp::getUserId());
 
-    // Update UI
-    scroll_list->deleteSelectedItems();
-    scroll_list->selectFirstItem();
-    if (scroll_list->getFirstSelectedIndex() == -1)
+        LLPointer<LLCredential> cred = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid());
+        if (cred.notNull() && cred->userID() == LLStartUp::getUserId())
+        {
+            gSecAPIHandler->deleteCredential(cred);
+        }
+
+        // Clean favorites
+        LLFavoritesOrderStorage::removeFavoritesRecordOfUser(fav_id, LLGridManager::getInstance()->getGrid());
+
+        // mark data for removal
+        LLAppViewer::instance()->purgeUserDataOnExit();
+        LLAppViewer::instance()->requestQuit();
+    }
+    return false;
+}
+
+void LLFloaterForgetUser::loadGridToList(const std::string &grid, bool show_grid_name)
+{
+    std::string grid_label;
+    if (show_grid_name)
     {
-        LLButton *button = getChild<LLButton>("forget");
-        button->setEnabled(false);
-        chk_box->setEnabled(false);
+        grid_label = LLGridManager::getInstance()->getGridId(grid); //login id (shortened label)
+    }
+    if (gSecAPIHandler->hasCredentialMap("login_list", grid))
+    {
+        LLSecAPIHandler::credential_map_t credencials;
+        gSecAPIHandler->loadCredentialMap("login_list", grid, credencials);
+
+        LLSecAPIHandler::credential_map_t::iterator cr_iter = credencials.begin();
+        LLSecAPIHandler::credential_map_t::iterator cr_end = credencials.end();
+        while (cr_iter != cr_end)
+        {
+            if (cr_iter->second.notNull()) // basic safety
+            {
+                std::string user_label = LLPanelLogin::getUserName(cr_iter->second);
+                LLSD user_data;
+                user_data["user_id"] = cr_iter->first;
+                user_data["label"] = user_label;
+                user_data["grid"] = grid;
+
+                if (show_grid_name)
+                {
+                    user_label += " (" + grid_label + ")";
+                }
+
+                LLScrollListItem::Params item_params;
+                item_params.value(user_data);
+                item_params.columns.add()
+                    .value(user_label)
+                    .column("user")
+                    .font(LLFontGL::getFontSansSerifSmall());
+                mScrollList->addRow(item_params, ADD_BOTTOM);
+            }
+            cr_iter++;
+        }
+    }
+    else
+    {
+        // "Legacy" viewer support
+        LLPointer<LLCredential> cred = gSecAPIHandler->loadCredential(grid);
+        if (cred.notNull())
+        {
+            const LLSD &ident = cred->getIdentifier();
+            if (ident.isMap() && ident.has("type"))
+            {
+                std::string user_label = LLPanelLogin::getUserName(cred);
+                LLSD user_data;
+                user_data["user_id"] = cred->userID();
+                user_data["label"] = user_label;
+                user_data["grid"] = grid;
+
+                if (show_grid_name)
+                {
+                    user_label += " (" + grid_label + ")";
+                }
+
+                LLScrollListItem::Params item_params;
+                item_params.value(user_data);
+                item_params.columns.add()
+                    .value(user_label)
+                    .column("user")
+                    .font(LLFontGL::getFontSansSerifSmall());
+                mScrollList->addRow(item_params, ADD_BOTTOM);
+            }
+        }
     }
 }
 
diff --git a/indra/newview/llfloaterforgetuser.h b/indra/newview/llfloaterforgetuser.h
index 119aece2d10..0ab47d3d693 100644
--- a/indra/newview/llfloaterforgetuser.h
+++ b/indra/newview/llfloaterforgetuser.h
@@ -29,6 +29,8 @@
 
 #include "llfloater.h"
 
+class LLScrollListCtrl;
+
 class LLFloaterForgetUser : public LLFloater
 {
 public:
@@ -37,10 +39,15 @@ class LLFloaterForgetUser : public LLFloater
 
     BOOL postBuild();
     void onForgetClicked();
+    static void forgetUser(const std::string &userid, const std::string &fav_id, const std::string &grid, bool delete_data);
 
 private:
+    static bool onConfirmLogout(const LLSD& notification, const LLSD& response, const std::string &favorites_id);
+    void loadGridToList(const std::string &grid, bool show_grid_name);
+
+    LLScrollListCtrl *mScrollList;
+
     bool mLoginPanelDirty;
-    std::string mGrid;
 };
 
 #endif
diff --git a/indra/newview/llsecapi.h b/indra/newview/llsecapi.h
index c0f0a367c75..5cde0d549eb 100644
--- a/indra/newview/llsecapi.h
+++ b/indra/newview/llsecapi.h
@@ -491,6 +491,10 @@ class LLSecAPIHandler : public LLThreadSafeRefCount
 	virtual bool hasCredentialMap(const std::string& storage,
 								  const std::string& grid)=0;
 
+	// returns true im map is empty or does not exist
+	virtual bool emptyCredentialMap(const std::string& storage,
+									const std::string& grid)=0;
+
 	// load map of credentials from specific storage
 	typedef std::map<std::string, LLPointer<LLCredential> > credential_map_t;
 	virtual void loadCredentialMap(const std::string& storage,
diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp
index a8bb54a90ed..2932ae45a3a 100644
--- a/indra/newview/llsechandler_basic.cpp
+++ b/indra/newview/llsechandler_basic.cpp
@@ -1664,6 +1664,19 @@ bool LLSecAPIBasicHandler::hasCredentialMap(const std::string& storage, const st
     return credential.isMap();
 }
 
+// returns true if map is empty or does not exist
+bool LLSecAPIBasicHandler::emptyCredentialMap(const std::string& storage, const std::string& grid)
+{
+    if (storage == DEFAULT_CREDENTIAL_STORAGE)
+    {
+        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
+    }
+
+    LLSD credential = getProtectedData(storage, grid);
+
+    return !credential.isMap() || credential.emptyMap();
+}
+
 // Load map of credentials from specified credential store, given the grid
 void LLSecAPIBasicHandler::loadCredentialMap(const std::string& storage, const std::string& grid, credential_map_t& credential_map)
 {
diff --git a/indra/newview/llsechandler_basic.h b/indra/newview/llsechandler_basic.h
index 426b5d392ae..0bc7f5230f8 100644
--- a/indra/newview/llsechandler_basic.h
+++ b/indra/newview/llsechandler_basic.h
@@ -293,6 +293,10 @@ class LLSecAPIBasicHandler : public LLSecAPIHandler
 	virtual bool hasCredentialMap(const std::string& storage,
 								  const std::string& grid);
 
+	// returns true if map is empty or does not exist
+	virtual bool emptyCredentialMap(const std::string& storage,
+									const std::string& grid);
+
 	// load map of credentials from specific storage
 	virtual void loadCredentialMap(const std::string& storage,
 								   const std::string& grid,
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 08012686f99..684d3bd4218 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -2715,6 +2715,15 @@ std::string& LLStartUp::getInitialOutfitName()
 	return sInitialOutfit;
 }
 
+std::string LLStartUp::getUserId()
+{
+    if (gUserCredential.isNull())
+    {
+        return "";
+    }
+    return gUserCredential->userID();
+}
+
 // Loads a bitmap to display during load
 void init_start_screen(S32 location_id)
 {
diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h
index db372070228..5ce74b8fae7 100644
--- a/indra/newview/llstartup.h
+++ b/indra/newview/llstartup.h
@@ -115,6 +115,7 @@ class LLStartUp
 	static void saveInitialOutfit();
 
 	static std::string& getInitialOutfitName();
+	static std::string getUserId();
 	
 	static bool dispatchURL();
 		// if we have a SLURL or sim string ("Ahern/123/45") that started
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 70f31394889..d99f5fa7d96 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -3558,6 +3558,18 @@ If this is your first time using [SECOND_LIFE], you will need to create an accou
 Already remembered user can be forgotten from Me &gt; Preferences &gt; Advanced &gt; Remembered Usernames.
   </notification>
 
+  <notification
+   icon="alertmodal.tga"
+   name="LoginCantRemoveCurUsername"
+   type="alertmodal">
+    <tag>confirm</tag>
+Forgetting the logged-in user requires you to log out.
+    <usetemplate
+     name="okcancelbuttons"
+     notext="Cancel"
+     yestext="Confirm and log out"/>
+  </notification>
+
   <notification
    icon="alertmodal.tga"
    name="LoginPacketNeverReceived"
-- 
GitLab