2
0
mirror of https://github.com/meganz/MEGAcmd synced 2025-08-22 01:47:24 +00:00

Merge branch 'task/CMD-620_folder_link_implicit_resume' into 'develop'

CMD-620. Allow explicit --resume for folder links logins

Closes CMD-620

See merge request apps/MEGAcmd!873
This commit is contained in:
Pablo M 2025-03-20 02:19:45 +13:00
commit e85a3e42db
6 changed files with 155 additions and 94 deletions

View File

@ -20,6 +20,10 @@
#include "configurationmanager.h"
#include "megacmdutils.h"
#ifdef MEGACMD_TESTING_CODE
#include "../tests/common/Instruments.h"
#endif
#include <utility>
using namespace mega;
@ -642,6 +646,9 @@ void MegaCmdListener::onRequestUpdate(MegaApi* api, MegaRequest *request)
{
case MegaRequest::TYPE_FETCH_NODES:
{
#ifdef MEGACMD_TESTING_CODE
TestInstruments::Instance().fireEvent(TestInstruments::Event::FETCH_NODES_REQ_UPDATE);
#endif
unsigned int cols = getNumberOfCols(80);
string outputString;
outputString.resize(cols+1);

View File

@ -777,6 +777,7 @@ void insertValidParamsPerCommand(set<string> *validParams, string thecommand, se
validOptValues->insert("auth-code");
validOptValues->insert("auth-key");
validOptValues->insert("password");
validOptValues->insert("resume");
}
else if ("psa" == thecommand)
{
@ -1541,13 +1542,13 @@ const char * getUsageStr(const char *command, const HelpFlags& flags)
if (isCurrentThreadInteractive())
{
return "login [--auth-code=XXXX] [email [password]] | exportedfolderurl#key"
" [--auth-key=XXXX] | passwordprotectedlink [--password=PASSWORD]"
" [--auth-key=XXXX] [--resume] | passwordprotectedlink [--password=PASSWORD]"
" | session";
}
else
{
return "login [--auth-code=XXXX] email password | exportedfolderurl#key"
" [--auth-key=XXXX] | passwordprotectedlink [--password=PASSWORD]"
" [--auth-key=XXXX] [--resume] | passwordprotectedlink [--password=PASSWORD]"
" | session";
}
}
@ -2072,7 +2073,7 @@ string getHelpStr(const char *command, const HelpFlags& flags = {})
os << "Usage: " << getUsageStr(command, flags) << endl;
if (!strcmp(command, "login"))
{
os << "Logs into a MEGA account or folder link. You can only log into one entity at a time." << endl;
os << "Logs into a MEGA account, folder link or a previous session. You can only log into one entity at a time." << endl;
os << "Logging into a MEGA account:" << endl;
os << "\tYou can log into a MEGA account by providing either a session ID or a username and password. A session "
"ID simply identifies a session that you have previously logged in with using a username and password; "
@ -2091,6 +2092,10 @@ string getHelpStr(const char *command, const HelpFlags& flags = {})
"the password for that link." << endl;
os << "\t--auth-key=AUTHKEY: If the link is a writable folder link, then this option allows you to log in with "
"write privileges. Without this option, you will log into the link with read access only." << endl;
os << "\t--resume: A convenience option to try to resume from cache. When login into a folder, contrary to what occurs with login into a user account,"
" MEGAcmd will not try to load anything from cache: loading everything from scratch. This option changes that. Note, "
"login using a session string, will of course, try to load from cache. This option may be convinient, for instance, if you previously "
"logged out using --keep-session." << endl;
os << endl;
os << "For more information about MEGA folder links, see \"" << getCommandPrefixBasedOnMode() << "export --help\"." << endl;
}
@ -2330,7 +2335,8 @@ string getHelpStr(const char *command, const HelpFlags& flags = {})
os << "Logs out" << endl;
os << endl;
os << "Options:" << endl;
os << " --keep-session" << "\t" << "Keeps the current session." << endl;
os << " --keep-session" << "\t" << "Keeps the current session. This will also prevent the deletion of cached data associated "
"with current session." << endl;
}
else if (!strcmp(command, "import"))
{

View File

@ -8012,99 +8012,104 @@ void MegaCmdExecuter::executecommand(vector<string> words, map<string, int> *clf
LoginGuard loginGuard;
int clientID = getintOption(cloptions, "clientID", -1);
if (!api->isLoggedIn())
{
if (words.size() > 1)
{
if (strchr(words[1].c_str(), '@'))
{
// full account login
if (words.size() > 2)
{
MegaCmdListener *megaCmdListener = new MegaCmdListener(NULL,NULL,clientID);
sandboxCMD->resetSandBox();
api->login(words[1].c_str(), words[2].c_str(), megaCmdListener);
if (actUponLogin(megaCmdListener) == MegaError::API_EMFAREQUIRED )
{
MegaCmdListener *megaCmdListener2 = new MegaCmdListener(NULL,NULL,clientID);
string pin2fa = getOption(cloptions, "auth-code", "");
if (!pin2fa.size())
{
pin2fa = askforUserResponse("Enter the code generated by your authentication app: ");
}
LOG_verbose << " Using confirmation pin: " << pin2fa;
api->multiFactorAuthLogin(words[1].c_str(), words[2].c_str(), pin2fa.c_str(), megaCmdListener2);
actUponLogin(megaCmdListener2);
delete megaCmdListener2;
return;
}
delete megaCmdListener;
}
else
{
login = words[1];
if (isCurrentThreadInteractive())
{
setprompt(LOGINPASSWORD);
}
else
{
setCurrentThreadOutCode(MCMD_EARGS);
LOG_err << "Extra args required in non-interactive mode. Usage: " << getUsageStr("login");
}
}
}
else
{
const char* ptr;
if (( ptr = strchr(words[1].c_str(), '#'))) // folder link indicator
{
string publicLink = words[1];
if (!decryptLinkIfEncrypted(api, publicLink, cloptions))
{
return;
}
MegaCmdListener *megaCmdListener = new MegaCmdListener(NULL);
sandboxCMD->resetSandBox();
string authKey = getOption(cloptions, "auth-key", "");
if (authKey.empty())
{
api->loginToFolder(publicLink.c_str(), megaCmdListener);
}
else
{
api->loginToFolder(publicLink.c_str(), authKey.c_str(), megaCmdListener);
}
actUponLogin(megaCmdListener);
delete megaCmdListener;
return;
}
else
{
LOG_info << "Resuming session...";
MegaCmdListener *megaCmdListener = new MegaCmdListener(NULL);
sandboxCMD->resetSandBox();
api->fastLogin(words[1].c_str(), megaCmdListener);
actUponLogin(megaCmdListener);
delete megaCmdListener;
return;
}
}
}
else
{
setCurrentThreadOutCode(MCMD_EARGS);
LOG_err << " " << getUsageStr("login");
}
}
else
if (api->isLoggedIn())
{
setCurrentThreadOutCode(MCMD_INVALIDSTATE);
LOG_err << "Already logged in. Please log out first.";
return;
}
if (words.size() < 2)
{
setCurrentThreadOutCode(MCMD_EARGS);
LOG_err << " " << getUsageStr("login");
return;
}
bool accountLogin = words[1].find('@') != std::string::npos;
bool folderLinkLogin = !accountLogin && words[1].find('#') != std::string::npos;
bool resumeFolderLink = getFlag(clflags, "resume");
string authKey = getOption(cloptions, "auth-key", "");
if (!folderLinkLogin)
{
if (resumeFolderLink)
{
setCurrentThreadOutCode(MCMD_EARGS);
LOG_err << "Explicit resumption only required for folder links logins.";
return;
}
if (!authKey.empty())
{
setCurrentThreadOutCode(MCMD_EARGS);
LOG_err << "Auth key only required for login in writable folder links.";
return;
}
}
if (accountLogin)
{
if (words.size() > 2)
{
MegaCmdListener *megaCmdListener = new MegaCmdListener(NULL,NULL,clientID);
sandboxCMD->resetSandBox();
api->login(words[1].c_str(), words[2].c_str(), megaCmdListener);
if (actUponLogin(megaCmdListener) == MegaError::API_EMFAREQUIRED )
{
MegaCmdListener *megaCmdListener2 = new MegaCmdListener(NULL,NULL,clientID);
string pin2fa = getOption(cloptions, "auth-code", "");
if (!pin2fa.size())
{
pin2fa = askforUserResponse("Enter the code generated by your authentication app: ");
}
LOG_verbose << " Using confirmation pin: " << pin2fa;
api->multiFactorAuthLogin(words[1].c_str(), words[2].c_str(), pin2fa.c_str(), megaCmdListener2);
actUponLogin(megaCmdListener2);
delete megaCmdListener2;
return;
}
delete megaCmdListener;
}
else
{
login = words[1];
if (isCurrentThreadInteractive())
{
setprompt(LOGINPASSWORD);
}
else
{
setCurrentThreadOutCode(MCMD_EARGS);
LOG_err << "Extra args required in non-interactive mode. Usage: " << getUsageStr("login");
}
}
}
else if (folderLinkLogin) // folder link indicator
{
string publicLink = words[1];
if (!decryptLinkIfEncrypted(api, publicLink, cloptions))
{
return;
}
std::unique_ptr<MegaCmdListener>megaCmdListener = std::make_unique<MegaCmdListener>(nullptr);
sandboxCMD->resetSandBox();
api->loginToFolder(publicLink.c_str(), authKey.empty() ? nullptr : authKey.c_str(),
resumeFolderLink, megaCmdListener.get());
actUponLogin(megaCmdListener.get());
return;
}
else // session resumption
{
LOG_info << "Resuming session...";
MegaCmdListener *megaCmdListener = new MegaCmdListener(NULL);
sandboxCMD->resetSandBox();
api->fastLogin(words[1].c_str(), megaCmdListener);
actUponLogin(megaCmdListener);
delete megaCmdListener;
return;
}
return;

View File

@ -66,6 +66,7 @@ public:
{
SERVER_ABOUT_TO_START_WAITING_FOR_PETITIONS,
SYNC_ISSUES_LIST_UPDATED,
FETCH_NODES_REQ_UPDATE,
};
typedef std::function<void()> EventCallback;

View File

@ -32,6 +32,28 @@ TEST_F(NOINTERACTIVEBasicTest, Help)
executeInClient({"help"});
}
TEST_F(NOINTERACTIVENotLoggedTest, Folderlogin)
{
{
auto rLoging = executeInClient({"login", LINK_TESTEXPORTFOLDER});
ASSERT_TRUE(rLoging.ok());
}
{
auto rLogout = executeInClient({"logout", "--keep-session"});
ASSERT_TRUE(rLogout.ok());
}
{
EventTracker evTracker({TestInstruments::Event::FETCH_NODES_REQ_UPDATE});
auto rLoging = executeInClient({"login", "--resume", LINK_TESTEXPORTFOLDER});
ASSERT_TRUE(rLoging.ok());
ASSERT_STREQ(rLoging.out().c_str(), "");
ASSERT_STREQ(rLoging.err().c_str(), "");
ASSERT_FALSE(evTracker.eventHappened(TestInstruments::Event::FETCH_NODES_REQ_UPDATE));
}
}
TEST_F(NOINTERACTIVEReadTest, Find)
{
auto r = executeInClient({"find"});

View File

@ -88,6 +88,25 @@ class BasicGenericTest : public ::testing::Test
{
};
class NotLoggedTest : public BasicGenericTest
{
protected:
void SetUp() override
{
BasicGenericTest::SetUp();
auto result = executeInClient({"logout"}).ok();
ASSERT_TRUE(result);
}
void TearDown() override
{
BasicGenericTest::SetUp();
auto result = executeInClient({"logout"}).ok();
ASSERT_TRUE(result);
}
};
class LoggedInTest : public BasicGenericTest
{
protected:
@ -133,4 +152,5 @@ protected:
class NOINTERACTIVEBasicTest : public BasicGenericTest{};
class NOINTERACTIVELoggedInTest : public LoggedInTest{};
class NOINTERACTIVENotLoggedTest : public NotLoggedTest{};
class NOINTERACTIVEReadTest : public ReadTest{};