File indexing completed on 2024-12-08 04:33:09
0001 /* 0002 0003 * SPDX-FileCopyrightText: 2020 Alessandro Ambrosano <alessandro.ambrosano@gmail.com> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.0-or-later 0006 * 0007 */ 0008 0009 #include "ddpapi/ddpauthenticationmanager.h" 0010 0011 #include "ddpapi/ddpauthenticationmanagerutils.h" 0012 #include "ddpapi/ddpclient.h" 0013 0014 #include "ruqola_ddpapi_debug.h" 0015 0016 #include "utils.h" 0017 0018 #include <QByteArray> 0019 #include <QJsonArray> 0020 0021 #define sl(x) QStringLiteral(x) 0022 0023 QString DDPAuthenticationManager::METHOD_LOGIN = sl("login"); 0024 QString DDPAuthenticationManager::METHOD_SEND_OTP = sl("login"); 0025 QString DDPAuthenticationManager::METHOD_LOGOUT = sl("logout"); 0026 QString DDPAuthenticationManager::METHOD_LOGOUT_CLEAN_UP = sl("logoutCleanUp"); 0027 0028 DDPAuthenticationManager::DDPAuthenticationManager(DDPClient *ddpClient, QObject *parent) 0029 : DDPManager(ddpClient, parent) 0030 { 0031 connect(ddpClient, &DDPClient::connectedChanged, this, &DDPAuthenticationManager::clientConnectedChangedSlot); 0032 connect(ddpClient, &DDPClient::connecting, this, [this]() { 0033 setLoginStatus(LoginStatus::Connecting); 0034 }); 0035 } 0036 0037 DDPAuthenticationManager::~DDPAuthenticationManager() = default; 0038 0039 void DDPAuthenticationManager::setAuthToken(const QString &authToken) 0040 { 0041 mAuthToken = authToken; 0042 } 0043 0044 void DDPAuthenticationManager::login() 0045 { 0046 if (mAuthToken.isNull()) { 0047 qCWarning(RUQOLA_DDPAPI_LOG) << "No auth token available, can't login."; 0048 return; 0049 } 0050 0051 loginImpl(DDPAuthenticationManagerUtils::loginResume(mAuthToken)); 0052 } 0053 0054 void DDPAuthenticationManager::login(const QString &user, const QString &password) 0055 { 0056 loginImpl(DDPAuthenticationManagerUtils::login(user, password)); 0057 } 0058 0059 void DDPAuthenticationManager::loginLDAP(const QString &user, const QString &password) 0060 { 0061 loginImpl(DDPAuthenticationManagerUtils::loginLdap(user, password)); 0062 } 0063 0064 void DDPAuthenticationManager::loginOAuth(const QString &credentialToken, const QString &credentialSecret) 0065 { 0066 loginImpl(DDPAuthenticationManagerUtils::loginOAuth(credentialToken, credentialSecret)); 0067 } 0068 0069 void DDPAuthenticationManager::loginImpl(const QJsonArray ¶ms) 0070 { 0071 if (checkGenericError()) { 0072 return; 0073 } 0074 0075 if (mLoginStatus == LoginOngoing) { 0076 qCWarning(RUQOLA_DDPAPI_LOG) << "A login operation is already ongoing, dropping request."; 0077 return; 0078 } 0079 0080 if (mLoginStatus == LoggedIn) { 0081 qCWarning(RUQOLA_DDPAPI_LOG) << "User is already logged in on this server, ignoring."; 0082 return; 0083 } 0084 0085 // TODO: sanity checks on params 0086 0087 mLastLoginPayload = params[0].toObject(); 0088 ddpClient()->invokeMethodAndRegister(METHOD_LOGIN, params, this, static_cast<int>(Method::Login)); 0089 setLoginStatus(LoginStatus::LoginOngoing); 0090 } 0091 0092 void DDPAuthenticationManager::sendOTP(const QString &otpCode) 0093 { 0094 if (checkGenericError()) { 0095 return; 0096 } 0097 0098 if (mLoginStatus == LoginStatus::LoginOtpAuthOngoing) { 0099 qCWarning(RUQOLA_DDPAPI_LOG) << Q_FUNC_INFO << "Another OTP authentication is going on."; 0100 return; 0101 } 0102 0103 // if ((mLoginStatus != LoginStatus::LoginOtpRequired) && (mLoginStatus != LoginStatus::LoginFailedInvalidOtp)) { 0104 // qCWarning(RUQOLA_DDPAPI_LOG) << Q_FUNC_INFO << "Trying to send OTP but none was requested by the server."; 0105 // return; 0106 // } 0107 ddpClient()->invokeMethodAndRegister(METHOD_SEND_OTP, 0108 DDPAuthenticationManagerUtils::sendOTP(otpCode, mLastLoginPayload), 0109 this, 0110 static_cast<int>(Method::SendOtp)); 0111 setLoginStatus(LoginStatus::LoginOtpAuthOngoing); 0112 } 0113 0114 void DDPAuthenticationManager::logout() 0115 { 0116 if (checkGenericError()) { 0117 return; 0118 } 0119 0120 if (mLoginStatus == LoginStatus::LogoutOngoing) { 0121 qCWarning(RUQOLA_DDPAPI_LOG) << Q_FUNC_INFO << "Another logout operation is ongoing."; 0122 return; 0123 } 0124 0125 if (isLoggedOut()) { 0126 qCWarning(RUQOLA_DDPAPI_LOG) << Q_FUNC_INFO << "User is already logged out."; 0127 return; 0128 } 0129 0130 const QString params = sl("[]"); 0131 0132 ddpClient()->invokeMethodAndRegister(METHOD_LOGOUT, Utils::strToJsonArray(params), this, static_cast<int>(Method::SendOtp)); 0133 setLoginStatus(LoginStatus::LogoutOngoing); 0134 } 0135 0136 QString DDPAuthenticationManager::userId() const 0137 { 0138 return mUserId; 0139 } 0140 0141 QString DDPAuthenticationManager::authToken() const 0142 { 0143 return mAuthToken; 0144 } 0145 0146 void DDPAuthenticationManager::processMethodResponseImpl(int operationId, const QJsonObject &response) 0147 { 0148 switch (static_cast<Method>(operationId)) { 0149 case Method::Login: // intentional fall-through 0150 case Method::SendOtp: 0151 if (response.contains(sl("result"))) { 0152 const QJsonObject result = response[sl("result")].toObject(); 0153 mAuthToken = result[sl("token")].toString(); 0154 mUserId = result[sl("id")].toString(); 0155 mTokenExpires = result[sl("tokenExpires")].toObject()[sl("$date")].toDouble(); 0156 setLoginStatus(LoggedIn); 0157 } 0158 0159 if (response.contains(sl("error"))) { 0160 const QJsonValue errorCode = response[sl("error")].toObject()[sl("error")]; 0161 qCWarning(RUQOLA_DDPAPI_LOG) << "Login Error: " << response; 0162 // TODO: to be more user friendly, there would need to be more context 0163 // in case of a 403 error, as it may be received in different cases: 0164 // - When logging in with user and password -> invalid username or password 0165 // - When resuming an older login with an invalid / expired auth token -> invalid or expired token 0166 // - When logging in with an invalid / expired OAuth token (e.g. google, facebook, etc.) -> invalid or expired token 0167 if (errorCode.isDouble() && errorCode.toInt() == 403) { 0168 qCWarning(RUQOLA_DDPAPI_LOG) << "Invalid username or password."; 0169 setLoginStatus(LoginFailedInvalidUserOrPassword); 0170 } else if (errorCode.isString() && errorCode.toString() == sl("totp-required")) { 0171 qCWarning(RUQOLA_DDPAPI_LOG) << "Two factor authentication is enabled on the server." 0172 << "A one-time password is required to complete the login procedure."; 0173 setLoginStatus(LoginOtpRequired); 0174 } else if (errorCode.isString() && errorCode.toString() == sl("totp-invalid")) { 0175 qCWarning(RUQOLA_DDPAPI_LOG) << "Invalid OTP code."; 0176 setLoginStatus(LoginFailedInvalidOtp); 0177 } else if (errorCode.isString() && errorCode.toString() == sl("error-user-is-not-activated")) { 0178 qCWarning(RUQOLA_DDPAPI_LOG) << "User is not activated."; 0179 setLoginStatus(LoginFailedUserNotActivated); 0180 } else if (errorCode.isString() && errorCode.toString() == sl("error-login-blocked-for-ip")) { 0181 qCWarning(RUQOLA_DDPAPI_LOG) << "Login has been temporarily blocked For IP."; 0182 setLoginStatus(LoginFailedLoginBlockForIp); 0183 } else if (errorCode.isString() && errorCode.toString() == sl("error-login-blocked-for-user")) { 0184 qCWarning(RUQOLA_DDPAPI_LOG) << "Login has been temporarily blocked For User."; 0185 setLoginStatus(LoginFailedLoginBlockedForUser); 0186 } else if (errorCode.isString() && errorCode.toString() == sl("error-app-user-is-not-allowed-to-login")) { 0187 qCWarning(RUQOLA_DDPAPI_LOG) << "App user is not allowed to login."; 0188 setLoginStatus(LoginFailedLoginAppNotAllowedToLogin); 0189 } else { 0190 qCWarning(RUQOLA_DDPAPI_LOG) << "Generic error during login. Couldn't process" << response; 0191 setLoginStatus(GenericError); 0192 } 0193 return; 0194 } 0195 0196 break; 0197 0198 case Method::Logout: 0199 // Don't really expect any errors here, except maybe when logging out without 0200 // being logged in. That is being taken care of in DDPAuthenticationManager::logout. 0201 // Printing any error message that may come up just in case, and preventing any other 0202 // operations by switching to GenericError state. 0203 if (response.contains(sl("error"))) { 0204 qCWarning(RUQOLA_DDPAPI_LOG) << "Error while logging out. Server response:" << response; 0205 setLoginStatus(GenericError); 0206 return; 0207 } 0208 0209 setLoginStatus(LoggedOut); 0210 break; 0211 0212 case Method::LogoutCleanUp: 0213 // Maybe the clean up request payload is corrupted 0214 if (response.contains(sl("error"))) { 0215 const QJsonValue errorCode = response[sl("error")].toObject()[sl("error")]; 0216 qCWarning(RUQOLA_DDPAPI_LOG) << "Couldn't clean up on logout. Server response:" << response << " error code " << errorCode; 0217 // If we get here we're likely getting something wrong from the UI. 0218 // Need to prevent any further operation from now on. 0219 setLoginStatus(GenericError); 0220 return; 0221 } 0222 0223 setLoginStatus(LoggedOutAndCleanedUp); 0224 break; 0225 } 0226 } 0227 0228 DDPAuthenticationManager::LoginStatus DDPAuthenticationManager::loginStatus() const 0229 { 0230 return mLoginStatus; 0231 } 0232 0233 bool DDPAuthenticationManager::isLoggedIn() const 0234 { 0235 return mLoginStatus == DDPAuthenticationManager::LoggedIn; 0236 } 0237 0238 bool DDPAuthenticationManager::isLoggedOut() const 0239 { 0240 return mLoginStatus == DDPAuthenticationManager::LoggedOut || mLoginStatus == DDPAuthenticationManager::LogoutCleanUpOngoing 0241 || mLoginStatus == DDPAuthenticationManager::LoggedOutAndCleanedUp; 0242 } 0243 0244 void DDPAuthenticationManager::setLoginStatus(DDPAuthenticationManager::LoginStatus status) 0245 { 0246 if (mLoginStatus != status) { 0247 mLoginStatus = status; 0248 Q_EMIT loginStatusChanged(); 0249 } 0250 } 0251 0252 qint64 DDPAuthenticationManager::tokenExpires() const 0253 { 0254 return mTokenExpires; 0255 } 0256 0257 void DDPAuthenticationManager::clientConnectedChangedSlot() 0258 { 0259 if (mLoginStatus == DDPAuthenticationManager::FailedToLoginPluginProblem) { 0260 return; 0261 } 0262 if (checkGenericError()) { 0263 return; 0264 } 0265 // Just connected -> not logged in yet -> state = LoggedOut 0266 // Just disconnected -> whatever state we're in, need to change to LoggedOut 0267 setLoginStatus(LoginStatus::LoggedOut); 0268 } 0269 0270 bool DDPAuthenticationManager::checkGenericError() const 0271 { 0272 if (mLoginStatus == LoginStatus::GenericError) { 0273 qCWarning(RUQOLA_DDPAPI_LOG) << Q_FUNC_INFO << "The authentication manager is in an irreversible error state and can't perform any operation."; 0274 } 0275 0276 return mLoginStatus == LoginStatus::GenericError; 0277 } 0278 0279 #undef sl 0280 0281 #include "moc_ddpauthenticationmanager.cpp"