mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Convert the LoginClient to kotlin (#5479)
* Convert the result classes to kotlin * Convert response and callback to kotlin * Cleanup code-quality warnings before converting * Converted the LoginClient to kotlin * Updated the UserExtendedInfoClientTest to be kotlin, and live in the correct spot
This commit is contained in:
parent
0541aacdff
commit
02ce017c98
14 changed files with 370 additions and 450 deletions
|
|
@ -31,11 +31,10 @@ import fr.free.nrw.commons.auth.login.LoginResult;
|
|||
import fr.free.nrw.commons.databinding.ActivityLoginBinding;
|
||||
import fr.free.nrw.commons.utils.ActivityUtils;
|
||||
import java.util.Locale;
|
||||
import org.wikipedia.AppAdapter;
|
||||
import org.wikipedia.dataclient.ServiceFactory;
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||
import fr.free.nrw.commons.auth.login.LoginClient.LoginCallback;
|
||||
import fr.free.nrw.commons.auth.login.LoginCallback;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import org.wikipedia.dataclient.SharedPreferenceCookieManager
|
|||
import org.wikipedia.dataclient.WikiSite
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import fr.free.nrw.commons.auth.login.LoginClient
|
||||
import fr.free.nrw.commons.auth.login.LoginClient.LoginCallback
|
||||
import fr.free.nrw.commons.auth.login.LoginClient.LoginFailedException
|
||||
import fr.free.nrw.commons.auth.login.LoginCallback
|
||||
import fr.free.nrw.commons.auth.login.LoginFailedException
|
||||
import fr.free.nrw.commons.auth.login.LoginResult
|
||||
import retrofit2.Call
|
||||
import retrofit2.Response
|
||||
|
|
@ -129,7 +129,7 @@ class CsrfTokenClient(
|
|||
) = LoginClient()
|
||||
.request(csrfWikiSite, username, password, object : LoginCallback {
|
||||
override fun success(loginResult: LoginResult) {
|
||||
if (loginResult.pass()) {
|
||||
if (loginResult.pass) {
|
||||
sessionManager.updateAccount(loginResult)
|
||||
retryCallback()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package fr.free.nrw.commons.auth.login
|
||||
|
||||
interface LoginCallback {
|
||||
fun success(loginResult: LoginResult)
|
||||
fun twoFactorPrompt(caught: Throwable, token: String?)
|
||||
fun passwordResetPrompt(token: String?)
|
||||
fun error(caught: Throwable)
|
||||
}
|
||||
|
|
@ -1,259 +0,0 @@
|
|||
package fr.free.nrw.commons.auth.login;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.wikipedia.dataclient.Service;
|
||||
import org.wikipedia.dataclient.ServiceFactory;
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
import org.wikipedia.dataclient.mwapi.ListUserResponse;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||
import org.wikipedia.dataclient.mwapi.MwServiceError;
|
||||
import org.wikipedia.util.log.L;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
/**
|
||||
* Responsible for making login related requests to the server.
|
||||
*/
|
||||
public class LoginClient {
|
||||
@Nullable private Call<MwQueryResponse> tokenCall;
|
||||
@Nullable private Call<LoginResponse> loginCall;
|
||||
/**
|
||||
* userLanguage
|
||||
* It holds the value of the user's device language code.
|
||||
* For example, if user's device language is English it will hold En
|
||||
* The value will be fetched when the user clicks Login Button in the LoginActivity
|
||||
*/
|
||||
@NonNull private String userLanguage;
|
||||
|
||||
public interface LoginCallback {
|
||||
void success(@NonNull LoginResult result);
|
||||
void twoFactorPrompt(@NonNull Throwable caught, @Nullable String token);
|
||||
void passwordResetPrompt(@Nullable String token);
|
||||
void error(@NonNull Throwable caught);
|
||||
}
|
||||
|
||||
public void request(@NonNull final WikiSite wiki, @NonNull final String userName,
|
||||
@NonNull final String password, @NonNull final LoginCallback cb) {
|
||||
cancel();
|
||||
|
||||
tokenCall = ServiceFactory.get(wiki, LoginInterface.class).getLoginToken();
|
||||
tokenCall.enqueue(new Callback<MwQueryResponse>() {
|
||||
@Override public void onResponse(@NonNull Call<MwQueryResponse> call,
|
||||
@NonNull Response<MwQueryResponse> response) {
|
||||
login(wiki, userName, password, null, null, response.body().query().loginToken(),
|
||||
userLanguage, cb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<MwQueryResponse> call, @NonNull Throwable caught) {
|
||||
if (call.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
cb.error(caught);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void login(@NonNull final WikiSite wiki, @NonNull final String userName, @NonNull final String password,
|
||||
@Nullable final String retypedPassword, @Nullable final String twoFactorCode,
|
||||
@Nullable final String loginToken, @NonNull final String userLanguage, @NonNull final LoginCallback cb) {
|
||||
this.userLanguage = userLanguage;
|
||||
loginCall = TextUtils.isEmpty(twoFactorCode) && TextUtils.isEmpty(retypedPassword)
|
||||
? ServiceFactory.get(wiki, LoginInterface.class).postLogIn(userName, password, loginToken, userLanguage, Service.WIKIPEDIA_URL)
|
||||
: ServiceFactory.get(wiki, LoginInterface.class).postLogIn(userName, password, retypedPassword, twoFactorCode, loginToken,
|
||||
userLanguage, true);
|
||||
loginCall.enqueue(new Callback<LoginResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<LoginResponse> call, @NonNull Response<LoginResponse> response) {
|
||||
LoginResponse loginResponse = response.body();
|
||||
LoginResult loginResult = loginResponse.toLoginResult(wiki, password);
|
||||
if (loginResult != null) {
|
||||
if (loginResult.pass() && !TextUtils.isEmpty(loginResult.getUserName())) {
|
||||
// The server could do some transformations on user names, e.g. on some
|
||||
// wikis is uppercases the first letter.
|
||||
String actualUserName = loginResult.getUserName();
|
||||
getExtendedInfo(wiki, actualUserName, loginResult, cb);
|
||||
} else if ("UI".equals(loginResult.getStatus())) {
|
||||
if (loginResult instanceof LoginOAuthResult) {
|
||||
cb.twoFactorPrompt(new LoginFailedException(loginResult.getMessage()), loginToken);
|
||||
} else if (loginResult instanceof LoginResetPasswordResult) {
|
||||
cb.passwordResetPrompt(loginToken);
|
||||
} else {
|
||||
cb.error(new LoginFailedException(loginResult.getMessage()));
|
||||
}
|
||||
} else {
|
||||
cb.error(new LoginFailedException(loginResult.getMessage()));
|
||||
}
|
||||
} else {
|
||||
cb.error(new IOException("Login failed. Unexpected response."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<LoginResponse> call, @NonNull Throwable t) {
|
||||
if (call.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
cb.error(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loginBlocking(@NonNull final WikiSite wiki, @NonNull final String userName,
|
||||
@NonNull final String password, @Nullable final String twoFactorCode) throws Throwable {
|
||||
Response<MwQueryResponse> tokenResponse = ServiceFactory.get(wiki, LoginInterface.class).getLoginToken().execute();
|
||||
if (tokenResponse.body() == null || TextUtils.isEmpty(tokenResponse.body().query().loginToken())) {
|
||||
throw new IOException("Unexpected response when getting login token.");
|
||||
}
|
||||
String loginToken = tokenResponse.body().query().loginToken();
|
||||
|
||||
Call<LoginResponse> tempLoginCall = StringUtils.defaultIfEmpty(twoFactorCode, "").isEmpty()
|
||||
? ServiceFactory.get(wiki, LoginInterface.class).postLogIn(userName, password, loginToken, userLanguage, Service.WIKIPEDIA_URL)
|
||||
: ServiceFactory.get(wiki, LoginInterface.class).postLogIn(userName, password, null, twoFactorCode, loginToken,
|
||||
userLanguage, true);
|
||||
Response<LoginResponse> response = tempLoginCall.execute();
|
||||
LoginResponse loginResponse = response.body();
|
||||
if (loginResponse == null) {
|
||||
throw new IOException("Unexpected response when logging in.");
|
||||
}
|
||||
LoginResult loginResult = loginResponse.toLoginResult(wiki, password);
|
||||
if (loginResult == null) {
|
||||
throw new IOException("Unexpected response when logging in.");
|
||||
}
|
||||
if ("UI".equals(loginResult.getStatus())) {
|
||||
if (loginResult instanceof LoginOAuthResult) {
|
||||
|
||||
// TODO: Find a better way to boil up the warning about 2FA
|
||||
throw new LoginFailedException(loginResult.getMessage());
|
||||
|
||||
} else {
|
||||
throw new LoginFailedException(loginResult.getMessage());
|
||||
}
|
||||
} else if (!loginResult.pass() || TextUtils.isEmpty(loginResult.getUserName())) {
|
||||
throw new LoginFailedException(loginResult.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void getExtendedInfo(@NonNull final WikiSite wiki, @NonNull String userName,
|
||||
@NonNull final LoginResult loginResult, @NonNull final LoginCallback cb) {
|
||||
ServiceFactory.get(wiki, LoginInterface.class).getUserInfo(userName)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(response -> {
|
||||
ListUserResponse user = response.query().getUserResponse(userName);
|
||||
int id = response.query().userInfo().id();
|
||||
loginResult.setUserId(id);
|
||||
loginResult.setGroups(user.getGroups());
|
||||
cb.success(loginResult);
|
||||
L.v("Found user ID " + id + " for " + wiki.subdomain());
|
||||
}, caught -> {
|
||||
L.e("Login succeeded but getting group information failed. " + caught);
|
||||
cb.error(caught);
|
||||
});
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancelTokenRequest();
|
||||
cancelLogin();
|
||||
}
|
||||
|
||||
private void cancelTokenRequest() {
|
||||
if (tokenCall == null) {
|
||||
return;
|
||||
}
|
||||
tokenCall.cancel();
|
||||
tokenCall = null;
|
||||
}
|
||||
|
||||
private void cancelLogin() {
|
||||
if (loginCall == null) {
|
||||
return;
|
||||
}
|
||||
loginCall.cancel();
|
||||
loginCall = null;
|
||||
}
|
||||
|
||||
public static final class LoginResponse {
|
||||
@SuppressWarnings("unused") @SerializedName("error") @Nullable
|
||||
private MwServiceError error;
|
||||
|
||||
@SuppressWarnings("unused") @SerializedName("clientlogin") @Nullable
|
||||
private ClientLogin clientLogin;
|
||||
|
||||
@Nullable public MwServiceError getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
@Nullable LoginResult toLoginResult(@NonNull WikiSite site, @NonNull String password) {
|
||||
return clientLogin != null ? clientLogin.toLoginResult(site, password) : null;
|
||||
}
|
||||
|
||||
private static class ClientLogin {
|
||||
@SuppressWarnings("unused,NullableProblems") @NonNull private String status;
|
||||
@SuppressWarnings("unused") @Nullable private List<Request> requests;
|
||||
@SuppressWarnings("unused") @Nullable private String message;
|
||||
@SuppressWarnings("unused") @SerializedName("username") @Nullable private String userName;
|
||||
|
||||
LoginResult toLoginResult(@NonNull WikiSite site, @NonNull String password) {
|
||||
String userMessage = message;
|
||||
if ("UI".equals(status)) {
|
||||
if (requests != null) {
|
||||
for (Request req : requests) {
|
||||
if ("MediaWiki\\Extension\\OATHAuth\\Auth\\TOTPAuthenticationRequest".equals(req.id())) {
|
||||
return new LoginOAuthResult(site, status, userName, password, message);
|
||||
} else if ("MediaWiki\\Auth\\PasswordAuthenticationRequest".equals(req.id())) {
|
||||
return new LoginResetPasswordResult(site, status, userName, password, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!"PASS".equals(status) && !"FAIL".equals(status)) {
|
||||
//TODO: String resource -- Looks like needed for others in this class too
|
||||
userMessage = "An unknown error occurred.";
|
||||
}
|
||||
return new LoginResult(site, status, userName, password, userMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Request {
|
||||
@SuppressWarnings("unused") @Nullable private String id;
|
||||
//@SuppressWarnings("unused") @Nullable private JsonObject metadata;
|
||||
@SuppressWarnings("unused") @Nullable private String required;
|
||||
@SuppressWarnings("unused") @Nullable private String provider;
|
||||
@SuppressWarnings("unused") @Nullable private String account;
|
||||
@SuppressWarnings("unused") @Nullable private Map<String, RequestField> fields;
|
||||
|
||||
@Nullable String id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
private static class RequestField {
|
||||
@SuppressWarnings("unused") @Nullable private String type;
|
||||
@SuppressWarnings("unused") @Nullable private String label;
|
||||
@SuppressWarnings("unused") @Nullable private String help;
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginFailedException extends Throwable {
|
||||
public LoginFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
172
app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt
Normal file
172
app/src/main/java/fr/free/nrw/commons/auth/login/LoginClient.kt
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
package fr.free.nrw.commons.auth.login
|
||||
|
||||
import android.text.TextUtils
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.wikipedia.dataclient.Service
|
||||
import org.wikipedia.dataclient.ServiceFactory
|
||||
import org.wikipedia.dataclient.WikiSite
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Responsible for making login related requests to the server.
|
||||
*/
|
||||
class LoginClient {
|
||||
private var tokenCall: Call<MwQueryResponse?>? = null
|
||||
private var loginCall: Call<LoginResponse?>? = null
|
||||
|
||||
/**
|
||||
* userLanguage
|
||||
* It holds the value of the user's device language code.
|
||||
* For example, if user's device language is English it will hold En
|
||||
* The value will be fetched when the user clicks Login Button in the LoginActivity
|
||||
*/
|
||||
private var userLanguage = ""
|
||||
|
||||
fun request(wiki: WikiSite, userName: String, password: String, cb: LoginCallback) {
|
||||
cancel()
|
||||
|
||||
tokenCall = ServiceFactory.get(wiki, LoginInterface::class.java).getLoginToken()
|
||||
tokenCall!!.enqueue(object : Callback<MwQueryResponse?> {
|
||||
override fun onResponse(call: Call<MwQueryResponse?>, response: Response<MwQueryResponse?>) {
|
||||
login(wiki, userName, password, null, null,
|
||||
response.body()!!.query()!!.loginToken(), userLanguage, cb)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<MwQueryResponse?>, caught: Throwable) {
|
||||
if (call.isCanceled) {
|
||||
return
|
||||
}
|
||||
cb.error(caught)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun login(
|
||||
wiki: WikiSite, userName: String, password: String, retypedPassword: String?,
|
||||
twoFactorCode: String?, loginToken: String?, userLanguage: String, cb: LoginCallback
|
||||
) {
|
||||
this.userLanguage = userLanguage
|
||||
|
||||
loginCall = if (twoFactorCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) {
|
||||
ServiceFactory.get(wiki, LoginInterface::class.java)
|
||||
.postLogIn(userName, password, loginToken, userLanguage, Service.WIKIPEDIA_URL)
|
||||
} else {
|
||||
ServiceFactory.get(wiki, LoginInterface::class.java).postLogIn(
|
||||
userName, password, retypedPassword, twoFactorCode, loginToken, userLanguage, true
|
||||
)
|
||||
}
|
||||
|
||||
loginCall!!.enqueue(object : Callback<LoginResponse?> {
|
||||
override fun onResponse(
|
||||
call: Call<LoginResponse?>,
|
||||
response: Response<LoginResponse?>
|
||||
) {
|
||||
val loginResult = response.body()?.toLoginResult(wiki, password)
|
||||
if (loginResult != null) {
|
||||
if (loginResult.pass && !loginResult.userName.isNullOrEmpty()) {
|
||||
// The server could do some transformations on user names, e.g. on some
|
||||
// wikis is uppercases the first letter.
|
||||
getExtendedInfo(wiki, loginResult.userName, loginResult, cb)
|
||||
} else if ("UI" == loginResult.status) {
|
||||
when (loginResult) {
|
||||
is OAuthResult -> cb.twoFactorPrompt(
|
||||
LoginFailedException(loginResult.message),
|
||||
loginToken
|
||||
)
|
||||
|
||||
is ResetPasswordResult -> cb.passwordResetPrompt(loginToken)
|
||||
|
||||
is LoginResult.Result -> cb.error(
|
||||
LoginFailedException(loginResult.message)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
cb.error(LoginFailedException(loginResult.message))
|
||||
}
|
||||
} else {
|
||||
cb.error(IOException("Login failed. Unexpected response."))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<LoginResponse?>, t: Throwable) {
|
||||
if (call.isCanceled) {
|
||||
return
|
||||
}
|
||||
cb.error(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Throws(Throwable::class)
|
||||
fun loginBlocking(wiki: WikiSite, userName: String, password: String, twoFactorCode: String?) {
|
||||
val tokenResponse = ServiceFactory.get(wiki, LoginInterface::class.java).getLoginToken().execute()
|
||||
if (tokenResponse.body()?.query()?.loginToken().isNullOrEmpty()) {
|
||||
throw IOException("Unexpected response when getting login token.")
|
||||
}
|
||||
|
||||
val loginToken = tokenResponse.body()?.query()?.loginToken()
|
||||
val tempLoginCall = if (twoFactorCode.isNullOrEmpty()) {
|
||||
ServiceFactory.get(wiki, LoginInterface::class.java).postLogIn(
|
||||
userName, password, loginToken, userLanguage, Service.WIKIPEDIA_URL
|
||||
)
|
||||
} else {
|
||||
ServiceFactory.get(wiki, LoginInterface::class.java).postLogIn(
|
||||
userName, password, null, twoFactorCode, loginToken, userLanguage, true
|
||||
)
|
||||
}
|
||||
|
||||
val response = tempLoginCall.execute()
|
||||
val loginResponse = response.body() ?: throw IOException("Unexpected response when logging in.")
|
||||
val loginResult = loginResponse.toLoginResult(wiki, password) ?: throw IOException("Unexpected response when logging in.")
|
||||
|
||||
if ("UI" == loginResult.status) {
|
||||
if (loginResult is OAuthResult) {
|
||||
// TODO: Find a better way to boil up the warning about 2FA
|
||||
throw LoginFailedException(loginResult.message)
|
||||
}
|
||||
throw LoginFailedException(loginResult.message)
|
||||
}
|
||||
|
||||
if (!loginResult.pass || TextUtils.isEmpty(loginResult.userName)) {
|
||||
throw LoginFailedException(loginResult.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getExtendedInfo(
|
||||
wiki: WikiSite, userName: String, loginResult: LoginResult, cb: LoginCallback
|
||||
) = ServiceFactory.get(wiki, LoginInterface::class.java).getUserInfo(userName)
|
||||
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ response: MwQueryResponse? ->
|
||||
loginResult.userId = response?.query()?.userInfo()?.id() ?: 0
|
||||
loginResult.groups = response?.query()?.getUserResponse(userName)?.groups ?: emptySet()
|
||||
cb.success(loginResult)
|
||||
Timber.v(
|
||||
"Found user ID %s for %s",
|
||||
response?.query()?.userInfo()?.id(),
|
||||
wiki.subdomain()
|
||||
)
|
||||
}, { caught: Throwable ->
|
||||
Timber.e(caught, "Login succeeded but getting group information failed. ")
|
||||
cb.error(caught)
|
||||
})
|
||||
|
||||
fun cancel() {
|
||||
tokenCall?.let {
|
||||
it.cancel()
|
||||
tokenCall = null
|
||||
}
|
||||
|
||||
loginCall?.let {
|
||||
it.cancel()
|
||||
loginCall = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package fr.free.nrw.commons.auth.login
|
||||
|
||||
class LoginFailedException(message: String?) : Throwable(message)
|
||||
|
|
@ -25,7 +25,7 @@ interface LoginInterface {
|
|||
@Field("logintoken") token: String?,
|
||||
@Field("uselang") userLanguage: String?,
|
||||
@Field("loginreturnurl") url: String?
|
||||
): Call<LoginClient.LoginResponse?>
|
||||
): Call<LoginResponse?>
|
||||
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@FormUrlEncoded
|
||||
|
|
@ -38,7 +38,7 @@ interface LoginInterface {
|
|||
@Field("logintoken") token: String?,
|
||||
@Field("uselang") userLanguage: String?,
|
||||
@Field("logincontinue") loginContinue: Boolean
|
||||
): Call<LoginClient.LoginResponse?>
|
||||
): Call<LoginResponse?>
|
||||
|
||||
@GET(Service.MW_API_PREFIX + "action=query&meta=userinfo&list=users&usprop=groups|cancreate")
|
||||
fun getUserInfo(@Query("ususers") userName: String): Observable<MwQueryResponse?>
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
package fr.free.nrw.commons.auth.login;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
|
||||
public class LoginOAuthResult extends LoginResult {
|
||||
|
||||
public LoginOAuthResult(@NonNull WikiSite site, @NonNull String status, @Nullable String userName,
|
||||
@Nullable String password, @Nullable String message) {
|
||||
super(site, status, userName, password, message);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
package fr.free.nrw.commons.auth.login;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
|
||||
public class LoginResetPasswordResult extends LoginResult {
|
||||
public LoginResetPasswordResult(@NonNull WikiSite site, @NonNull String status, @Nullable String userName,
|
||||
@Nullable String password, @Nullable String message) {
|
||||
super(site, status, userName, password, message);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package fr.free.nrw.commons.auth.login
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
|
||||
import fr.free.nrw.commons.auth.login.LoginResult.Result
|
||||
import org.wikipedia.dataclient.WikiSite
|
||||
import org.wikipedia.dataclient.mwapi.MwServiceError
|
||||
|
||||
class LoginResponse {
|
||||
@SerializedName("error")
|
||||
val error: MwServiceError? = null
|
||||
|
||||
@SerializedName("clientlogin")
|
||||
private val clientLogin: ClientLogin? = null
|
||||
|
||||
fun toLoginResult(site: WikiSite, password: String): LoginResult? {
|
||||
return clientLogin?.toLoginResult(site, password)
|
||||
}
|
||||
}
|
||||
|
||||
internal class ClientLogin {
|
||||
private val status: String? = null
|
||||
private val requests: List<Request>? = null
|
||||
private val message: String? = null
|
||||
|
||||
@SerializedName("username")
|
||||
private val userName: String? = null
|
||||
|
||||
fun toLoginResult(site: WikiSite, password: String): LoginResult {
|
||||
var userMessage = message
|
||||
if ("UI" == status) {
|
||||
if (requests != null) {
|
||||
for (req in requests) {
|
||||
if ("MediaWiki\\Extension\\OATHAuth\\Auth\\TOTPAuthenticationRequest" == req.id()) {
|
||||
return OAuthResult(site, status, userName, password, message)
|
||||
} else if ("MediaWiki\\Auth\\PasswordAuthenticationRequest" == req.id()) {
|
||||
return ResetPasswordResult(site, status, userName, password, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("PASS" != status && "FAIL" != status) {
|
||||
//TODO: String resource -- Looks like needed for others in this class too
|
||||
userMessage = "An unknown error occurred."
|
||||
}
|
||||
return Result(site, status ?: "", userName, password, userMessage)
|
||||
}
|
||||
}
|
||||
|
||||
internal class Request {
|
||||
private val id: String? = null
|
||||
private val required: String? = null
|
||||
private val provider: String? = null
|
||||
private val account: String? = null
|
||||
private val fields: Map<String, RequestField>? = null
|
||||
|
||||
fun id(): String? = id
|
||||
}
|
||||
|
||||
internal class RequestField {
|
||||
private val type: String? = null
|
||||
private val label: String? = null
|
||||
private val help: String? = null
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
package fr.free.nrw.commons.auth.login;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
public class LoginResult {
|
||||
@NonNull private final WikiSite site;
|
||||
@NonNull private final String status;
|
||||
@Nullable private final String userName;
|
||||
@Nullable private final String password;
|
||||
@Nullable private final String message;
|
||||
|
||||
private int userId;
|
||||
@NonNull private Set<String> groups = Collections.emptySet();
|
||||
|
||||
public LoginResult(@NonNull WikiSite site, @NonNull String status, @Nullable String userName,
|
||||
@Nullable String password, @Nullable String message) {
|
||||
this.site = site;
|
||||
this.status = status;
|
||||
this.userName = userName;
|
||||
this.password = password;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@NonNull public WikiSite getSite() {
|
||||
return site;
|
||||
}
|
||||
|
||||
@NonNull public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean pass() {
|
||||
return "PASS".equals(status);
|
||||
}
|
||||
|
||||
public boolean fail() {
|
||||
return "FAIL".equals(status);
|
||||
}
|
||||
|
||||
@Nullable public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
@Nullable public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Nullable public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setUserId(int id) {
|
||||
this.userId = id;
|
||||
}
|
||||
|
||||
public int getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setGroups(@NonNull Set<String> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
@NonNull public Set<String> getGroups() {
|
||||
return groups;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package fr.free.nrw.commons.auth.login
|
||||
|
||||
import org.wikipedia.dataclient.WikiSite
|
||||
|
||||
sealed class LoginResult(
|
||||
val site: WikiSite,
|
||||
val status: String,
|
||||
val userName: String?,
|
||||
val password: String?,
|
||||
val message: String?
|
||||
) {
|
||||
var userId = 0
|
||||
var groups = emptySet<String>()
|
||||
val pass: Boolean get() = "PASS" == status
|
||||
|
||||
class Result(
|
||||
site: WikiSite,
|
||||
status: String,
|
||||
userName: String?,
|
||||
password: String?,
|
||||
message: String?
|
||||
): LoginResult(site, status, userName, password, message)
|
||||
|
||||
class OAuthResult(
|
||||
site: WikiSite,
|
||||
status: String,
|
||||
userName: String?,
|
||||
password: String?,
|
||||
message: String?
|
||||
) : LoginResult(site, status, userName, password, message)
|
||||
|
||||
class ResetPasswordResult(
|
||||
site: WikiSite,
|
||||
status: String,
|
||||
userName: String?,
|
||||
password: String?,
|
||||
message: String?
|
||||
) : LoginResult(site, status, userName, password, message)
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package fr.free.nrw.commons.auth.login
|
||||
|
||||
import android.net.Uri
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.stream.MalformedJsonException
|
||||
import fr.free.nrw.commons.MockWebServerTest
|
||||
import io.reactivex.observers.TestObserver
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.wikipedia.dataclient.WikiSite
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse
|
||||
import org.wikipedia.json.NamespaceTypeAdapter
|
||||
import org.wikipedia.json.PostProcessingTypeAdapter
|
||||
import org.wikipedia.json.UriTypeAdapter
|
||||
import org.wikipedia.json.WikiSiteTypeAdapter
|
||||
import org.wikipedia.page.Namespace
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
class UserExtendedInfoClientTest : MockWebServerTest() {
|
||||
private var apiService: LoginInterface? = null
|
||||
private val observer = TestObserver<MwQueryResponse>()
|
||||
private val gson = GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Uri::class.java, UriTypeAdapter().nullSafe())
|
||||
.registerTypeHierarchyAdapter(Namespace::class.java, NamespaceTypeAdapter().nullSafe())
|
||||
.registerTypeAdapter(WikiSite::class.java, WikiSiteTypeAdapter().nullSafe())
|
||||
.registerTypeAdapterFactory(PostProcessingTypeAdapter())
|
||||
.create()
|
||||
|
||||
@Before
|
||||
@Throws(Throwable::class)
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
|
||||
apiService = Retrofit.Builder()
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.baseUrl(server().url)
|
||||
.build()
|
||||
.create(LoginInterface::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Throwable::class)
|
||||
fun testRequestSuccess() {
|
||||
enqueueFromFile("user_extended_info.json")
|
||||
|
||||
apiService!!.getUserInfo("USER").subscribe(observer)
|
||||
|
||||
observer
|
||||
.assertComplete()
|
||||
.assertNoErrors()
|
||||
.assertValue { result: MwQueryResponse ->
|
||||
result.query()!!
|
||||
.userInfo()!!.id() == 24531888 && result.query()!!.getUserResponse("USER")!!
|
||||
.name() == "USER"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRequestResponse404() {
|
||||
enqueue404()
|
||||
|
||||
apiService!!.getUserInfo("USER").subscribe(observer)
|
||||
|
||||
observer.assertError(Exception::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRequestResponseMalformed() {
|
||||
enqueueMalformed()
|
||||
|
||||
apiService!!.getUserInfo("USER").subscribe(observer)
|
||||
|
||||
observer.assertError(MalformedJsonException::class.java)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
package fr.free.nrw.commons.login;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.stream.MalformedJsonException;
|
||||
import fr.free.nrw.commons.MockWebServerTest;
|
||||
import fr.free.nrw.commons.auth.login.LoginInterface;
|
||||
import io.reactivex.observers.TestObserver;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||
import org.wikipedia.json.NamespaceTypeAdapter;
|
||||
import org.wikipedia.json.PostProcessingTypeAdapter;
|
||||
import org.wikipedia.json.UriTypeAdapter;
|
||||
import org.wikipedia.json.WikiSiteTypeAdapter;
|
||||
import org.wikipedia.page.Namespace;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
public class UserExtendedInfoClientTest extends MockWebServerTest {
|
||||
|
||||
private LoginInterface apiService;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Throwable {
|
||||
super.setUp();
|
||||
|
||||
apiService = new Retrofit.Builder()
|
||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create(getGson()))
|
||||
.baseUrl(server().getUrl())
|
||||
.build()
|
||||
.create(LoginInterface.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestSuccess() throws Throwable {
|
||||
enqueueFromFile("user_extended_info.json");
|
||||
TestObserver<MwQueryResponse> observer = new TestObserver<>();
|
||||
|
||||
apiService.getUserInfo("USER").subscribe(observer);
|
||||
|
||||
observer
|
||||
.assertComplete()
|
||||
.assertNoErrors()
|
||||
.assertValue(
|
||||
result -> result.query().userInfo().id() == 24531888
|
||||
&& result.query().getUserResponse("USER").name().equals("USER")
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestResponse404() {
|
||||
enqueue404();
|
||||
TestObserver<MwQueryResponse> observer = new TestObserver<>();
|
||||
|
||||
apiService.getUserInfo("USER").subscribe(observer);
|
||||
|
||||
observer.assertError(Exception.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestResponseMalformed() {
|
||||
enqueueMalformed();
|
||||
TestObserver<MwQueryResponse> observer = new TestObserver<>();
|
||||
|
||||
apiService.getUserInfo("USER").subscribe(observer);
|
||||
|
||||
observer.assertError(MalformedJsonException.class);
|
||||
}
|
||||
|
||||
private Gson getGson() {
|
||||
return new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Uri.class, new UriTypeAdapter().nullSafe())
|
||||
.registerTypeHierarchyAdapter(Namespace.class, new NamespaceTypeAdapter().nullSafe())
|
||||
.registerTypeAdapter(WikiSite.class, new WikiSiteTypeAdapter().nullSafe())
|
||||
.registerTypeAdapterFactory(new PostProcessingTypeAdapter())
|
||||
.create();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue