mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge remote-tracking branch 'refs/remotes/origin/2.8-release'
This commit is contained in:
commit
23014e07c8
13 changed files with 459 additions and 85 deletions
|
|
@ -1,10 +1,15 @@
|
|||
package fr.free.nrw.commons.auth;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class AccountUtil {
|
||||
|
||||
public static final String ACCOUNT_TYPE = "fr.free.nrw.commons";
|
||||
public static final String AUTH_COOKIE = "authCookie";
|
||||
public static final String AUTH_TOKEN_TYPE = "CommonsAndroid";
|
||||
private final Context context;
|
||||
|
|
@ -13,4 +18,36 @@ public class AccountUtil {
|
|||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Account|null
|
||||
*/
|
||||
@Nullable
|
||||
public static Account account(Context context) {
|
||||
try {
|
||||
Account[] accounts = accountManager(context).getAccountsByType(BuildConfig.ACCOUNT_TYPE);
|
||||
if (accounts.length > 0) {
|
||||
return accounts[0];
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
Timber.e(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getUserName(Context context) {
|
||||
Account account = account(context);
|
||||
return account == null ? null : account.name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getPassword(Context context) {
|
||||
Account account = account(context);
|
||||
return account == null ? null : accountManager(context).getPassword(account);
|
||||
}
|
||||
|
||||
private static AccountManager accountManager(Context context) {
|
||||
return AccountManager.get(context);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ import timber.log.Timber;
|
|||
import static android.view.KeyEvent.KEYCODE_ENTER;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||
import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
|
||||
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE;
|
||||
|
||||
public class LoginActivity extends AccountAuthenticatorActivity {
|
||||
|
|
@ -242,7 +241,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
if (response != null) {
|
||||
Bundle authResult = new Bundle();
|
||||
authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username);
|
||||
authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
|
||||
authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, BuildConfig.ACCOUNT_TYPE);
|
||||
response.onResult(authResult);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,10 +76,6 @@ public class SessionManager {
|
|||
ContentResolver.setSyncAutomatically(account, BuildConfig.MODIFICATION_AUTHORITY, true); // Enable sync by default!
|
||||
}
|
||||
|
||||
private AccountManager accountManager() {
|
||||
return AccountManager.get(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Account|null
|
||||
*/
|
||||
|
|
@ -95,6 +91,22 @@ public class SessionManager {
|
|||
return currentAccount;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUserName() {
|
||||
Account account = getCurrentAccount();
|
||||
return account == null ? null : account.name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getPassword() {
|
||||
Account account = getCurrentAccount();
|
||||
return account == null ? null : accountManager().getPassword(account);
|
||||
}
|
||||
|
||||
private AccountManager accountManager() {
|
||||
return AccountManager.get(context);
|
||||
}
|
||||
|
||||
public Boolean revalidateAuthToken() {
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
Account curAccount = getCurrentAccount();
|
||||
|
|
@ -103,12 +115,13 @@ public class SessionManager {
|
|||
return false; // This should never happen
|
||||
}
|
||||
|
||||
accountManager.invalidateAuthToken(BuildConfig.ACCOUNT_TYPE, mediaWikiApi.getAuthCookie());
|
||||
accountManager.invalidateAuthToken(BuildConfig.ACCOUNT_TYPE, null);
|
||||
String authCookie = getAuthCookie();
|
||||
|
||||
if (authCookie == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mediaWikiApi.setAuthCookie(authCookie);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,7 @@ import android.support.annotation.NonNull;
|
|||
import android.support.annotation.Nullable;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
|
||||
import static fr.free.nrw.commons.auth.AccountUtil.ACCOUNT_TYPE;
|
||||
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE;
|
||||
|
||||
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||
|
|
@ -99,7 +96,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
|||
}
|
||||
|
||||
private boolean supportedAccountType(@Nullable String type) {
|
||||
return ACCOUNT_TYPE.equals(type);
|
||||
return BuildConfig.ACCOUNT_TYPE.equals(type);
|
||||
}
|
||||
|
||||
private Bundle addAccount(AccountAuthenticatorResponse response) {
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public class ContributionController {
|
|||
|
||||
public void handleImagePicked(int requestCode, @Nullable Uri uri, boolean isDirectUpload, String wikiDataEntityId) {
|
||||
FragmentActivity activity = fragment.getActivity();
|
||||
Timber.d("handleImagePicked() called with onActivityResult()");
|
||||
Timber.d("handleImagePicked() called with onActivityResult(). Boolean isDirectUpload: " + isDirectUpload + "String wikiDataEntityId: " + wikiDataEntityId);
|
||||
Intent shareIntent = new Intent(activity, ShareActivity.class);
|
||||
shareIntent.setAction(ACTION_SEND);
|
||||
switch (requestCode) {
|
||||
|
|
@ -113,21 +113,26 @@ public class ContributionController {
|
|||
shareIntent.setType("image/jpeg");
|
||||
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
|
||||
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Timber.i("Image selected");
|
||||
shareIntent.putExtra("isDirectUpload", isDirectUpload);
|
||||
Timber.d("Put extras into image intent, isDirectUpload is " + isDirectUpload);
|
||||
|
||||
try {
|
||||
shareIntent.putExtra("isDirectUpload", isDirectUpload);
|
||||
if (wikiDataEntityId != null && !wikiDataEntityId.equals("")) {
|
||||
shareIntent.putExtra(WIKIDATA_ENTITY_ID_PREF, wikiDataEntityId);
|
||||
}
|
||||
activity.startActivity(shareIntent);
|
||||
} catch (SecurityException e) {
|
||||
Timber.e(e, "Security Exception");
|
||||
}
|
||||
|
||||
if (activity != null) {
|
||||
activity.startActivity(shareIntent);
|
||||
}
|
||||
}
|
||||
|
||||
void saveState(Bundle outState) {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ import org.apache.http.params.BasicHttpParams;
|
|||
import org.apache.http.params.CoreProtocolPNames;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.json.JSONObject;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.mediawiki.api.MWApi;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
|
@ -37,7 +35,6 @@ import java.net.URL;
|
|||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
|
@ -48,6 +45,7 @@ import java.util.concurrent.Callable;
|
|||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.PageTitle;
|
||||
import fr.free.nrw.commons.auth.AccountUtil;
|
||||
import fr.free.nrw.commons.category.CategoryImageUtils;
|
||||
import fr.free.nrw.commons.category.QueryContinue;
|
||||
import fr.free.nrw.commons.notification.Notification;
|
||||
|
|
@ -72,8 +70,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
|
||||
private static final String THUMB_SIZE = "640";
|
||||
private AbstractHttpClient httpClient;
|
||||
private MWApi api;
|
||||
private MWApi wikidataApi;
|
||||
private CustomMwApi api;
|
||||
private CustomMwApi wikidataApi;
|
||||
private Context context;
|
||||
private SharedPreferences defaultPreferences;
|
||||
private SharedPreferences categoryPreferences;
|
||||
|
|
@ -94,9 +92,11 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
|
||||
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
|
||||
httpClient = new DefaultHttpClient(cm, params);
|
||||
httpClient.addRequestInterceptor(NetworkInterceptors.getHttpRequestInterceptor());
|
||||
api = new MWApi(apiURL, httpClient);
|
||||
wikidataApi = new MWApi(wikidatApiURL, httpClient);
|
||||
if (BuildConfig.DEBUG) {
|
||||
httpClient.addRequestInterceptor(NetworkInterceptors.getHttpRequestInterceptor());
|
||||
}
|
||||
api = new CustomMwApi(apiURL, httpClient);
|
||||
wikidataApi = new CustomMwApi(wikidatApiURL, httpClient);
|
||||
this.defaultPreferences = defaultPreferences;
|
||||
this.categoryPreferences = categoryPreferences;
|
||||
this.gson = gson;
|
||||
|
|
@ -161,25 +161,25 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param loginApiResult ApiResult Any clientlogin api result
|
||||
* @param loginCustomApiResult CustomApiResult Any clientlogin api result
|
||||
* @return String On success: "PASS"
|
||||
* continue: "2FA" (More information required for 2FA)
|
||||
* failure: A failure message code (defined by mediawiki)
|
||||
* misc: genericerror-UI, genericerror-REDIRECT, genericerror-RESTART
|
||||
*/
|
||||
private String getErrorCodeToReturn(ApiResult loginApiResult) {
|
||||
String status = loginApiResult.getString("/api/clientlogin/@status");
|
||||
private String getErrorCodeToReturn(CustomApiResult loginCustomApiResult) {
|
||||
String status = loginCustomApiResult.getString("/api/clientlogin/@status");
|
||||
if (status.equals("PASS")) {
|
||||
api.isLoggedIn = true;
|
||||
setAuthCookieOnLogin(true);
|
||||
return status;
|
||||
} else if (status.equals("FAIL")) {
|
||||
setAuthCookieOnLogin(false);
|
||||
return loginApiResult.getString("/api/clientlogin/@messagecode");
|
||||
return loginCustomApiResult.getString("/api/clientlogin/@messagecode");
|
||||
} else if (
|
||||
status.equals("UI")
|
||||
&& loginApiResult.getString("/api/clientlogin/requests/_v/@id").equals("TOTPAuthenticationRequest")
|
||||
&& loginApiResult.getString("/api/clientlogin/requests/_v/@provider").equals("Two-factor authentication (OATH).")
|
||||
&& loginCustomApiResult.getString("/api/clientlogin/requests/_v/@id").equals("TOTPAuthenticationRequest")
|
||||
&& loginCustomApiResult.getString("/api/clientlogin/requests/_v/@provider").equals("Two-factor authentication (OATH).")
|
||||
) {
|
||||
setAuthCookieOnLogin(false);
|
||||
return "2FA";
|
||||
|
|
@ -209,16 +209,26 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
public void setAuthCookie(String authCookie) {
|
||||
api.setAuthCookie(authCookie);
|
||||
|
||||
Timber.d("Mediawiki auth cookie is %s", api.getAuthCookie());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateLogin() throws IOException {
|
||||
return api.validateLogin();
|
||||
boolean validateLoginResp = api.validateLogin();
|
||||
Timber.d("Validate login response is %s", validateLoginResp);
|
||||
return validateLoginResp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEditToken() throws IOException {
|
||||
return api.getEditToken();
|
||||
String editToken = api.action("query")
|
||||
.param("centralauthtoken", getCentralAuthToken())
|
||||
.param("meta", "tokens")
|
||||
.post()
|
||||
.getString("/api/query/tokens/@csrftoken");
|
||||
Timber.d("MediaWiki edit token is %s", editToken);
|
||||
return editToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -227,6 +237,14 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
.get()
|
||||
.getString("/api/centralauthtoken/@centralauthtoken");
|
||||
Timber.d("MediaWiki Central auth token is %s", centralAuthToken);
|
||||
|
||||
if(centralAuthToken == null || centralAuthToken.isEmpty()) {
|
||||
api.removeAllCookies();
|
||||
String login = login(AccountUtil.getUserName(context), AccountUtil.getPassword(context));
|
||||
if(login.equals("PASS")) {
|
||||
return getCentralAuthToken();
|
||||
}
|
||||
}
|
||||
return centralAuthToken;
|
||||
}
|
||||
|
||||
|
|
@ -299,7 +317,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
@NonNull
|
||||
public MediaResult fetchMediaByFilename(String filename) throws IOException {
|
||||
ApiResult apiResult = api.action("query")
|
||||
CustomApiResult apiResult = api.action("query")
|
||||
.param("prop", "revisions")
|
||||
.param("titles", filename)
|
||||
.param("rvprop", "content")
|
||||
|
|
@ -316,7 +334,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@NonNull
|
||||
public Observable<String> searchCategories(String filterValue, int searchCatsLimit) {
|
||||
return Single.fromCallable(() -> {
|
||||
List<ApiResult> categoryNodes = null;
|
||||
List<CustomApiResult> categoryNodes = null;
|
||||
try {
|
||||
categoryNodes = api.action("query")
|
||||
.param("format", "xml")
|
||||
|
|
@ -336,7 +354,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
}
|
||||
|
||||
List<String> categories = new ArrayList<>();
|
||||
for (ApiResult categoryNode : categoryNodes) {
|
||||
for (CustomApiResult categoryNode : categoryNodes) {
|
||||
String cat = categoryNode.getDocument().getTextContent();
|
||||
String catString = cat.replace("Category:", "");
|
||||
categories.add(catString);
|
||||
|
|
@ -350,7 +368,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@NonNull
|
||||
public Observable<String> allCategories(String filterValue, int searchCatsLimit) {
|
||||
return Single.fromCallable(() -> {
|
||||
ArrayList<ApiResult> categoryNodes = null;
|
||||
ArrayList<CustomApiResult> categoryNodes = null;
|
||||
try {
|
||||
categoryNodes = api.action("query")
|
||||
.param("list", "allcategories")
|
||||
|
|
@ -367,7 +385,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
}
|
||||
|
||||
List<String> categories = new ArrayList<>();
|
||||
for (ApiResult categoryNode : categoryNodes) {
|
||||
for (CustomApiResult categoryNode : categoryNodes) {
|
||||
categories.add(categoryNode.getDocument().getTextContent());
|
||||
}
|
||||
|
||||
|
|
@ -375,16 +393,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
}).flatMapObservable(Observable::fromIterable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edit token for making wiki data edits
|
||||
* https://www.mediawiki.org/wiki/API:Tokens
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
private String getWikidataEditToken() throws IOException {
|
||||
return wikidataApi.getEditToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWikidataCsrfToken() throws IOException {
|
||||
String wikidataCsrfToken = wikidataApi.action("query")
|
||||
|
|
@ -411,7 +419,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
public String wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException {
|
||||
Timber.d("Filename is %s", value);
|
||||
ApiResult result = wikidataApi.action("wbcreateclaim")
|
||||
CustomApiResult result = wikidataApi.action("wbcreateclaim")
|
||||
.param("entity", entityId)
|
||||
.param("centralauthtoken", getCentralAuthToken())
|
||||
.param("token", getWikidataCsrfToken())
|
||||
|
|
@ -444,7 +452,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Nullable
|
||||
@Override
|
||||
public boolean addWikidataEditTag(String revisionId) throws IOException {
|
||||
ApiResult result = wikidataApi.action("tag")
|
||||
CustomApiResult result = wikidataApi.action("tag")
|
||||
.param("revid", revisionId)
|
||||
.param("centralauthtoken", getCentralAuthToken())
|
||||
.param("token", getWikidataCsrfToken())
|
||||
|
|
@ -471,7 +479,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@NonNull
|
||||
public Observable<String> searchTitles(String title, int searchCatsLimit) {
|
||||
return Single.fromCallable((Callable<List<String>>) () -> {
|
||||
ArrayList<ApiResult> categoryNodes;
|
||||
ArrayList<CustomApiResult> categoryNodes;
|
||||
|
||||
try {
|
||||
categoryNodes = api.action("query")
|
||||
|
|
@ -493,7 +501,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
}
|
||||
|
||||
List<String> titleCategories = new ArrayList<>();
|
||||
for (ApiResult categoryNode : categoryNodes) {
|
||||
for (CustomApiResult categoryNode : categoryNodes) {
|
||||
String cat = categoryNode.getDocument().getTextContent();
|
||||
String catString = cat.replace("Category:", "");
|
||||
titleCategories.add(catString);
|
||||
|
|
@ -506,7 +514,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
@NonNull
|
||||
public LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException {
|
||||
org.mediawiki.api.MWApi.RequestBuilder builder = api.action("query")
|
||||
CustomMwApi.RequestBuilder builder = api.action("query")
|
||||
.param("list", "logevents")
|
||||
.param("letype", "upload")
|
||||
.param("leprop", "title|timestamp|ids")
|
||||
|
|
@ -518,7 +526,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
if (!TextUtils.isEmpty(queryContinue)) {
|
||||
builder.param("lestart", queryContinue);
|
||||
}
|
||||
ApiResult result = builder.get();
|
||||
CustomApiResult result = builder.get();
|
||||
|
||||
return new LogEventResult(
|
||||
getLogEventsFromResult(result),
|
||||
|
|
@ -526,11 +534,11 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
private ArrayList<LogEventResult.LogEvent> getLogEventsFromResult(ApiResult result) {
|
||||
ArrayList<ApiResult> uploads = result.getNodes("/api/query/logevents/item");
|
||||
private ArrayList<LogEventResult.LogEvent> getLogEventsFromResult(CustomApiResult result) {
|
||||
ArrayList<CustomApiResult> uploads = result.getNodes("/api/query/logevents/item");
|
||||
Timber.d("%d results!", uploads.size());
|
||||
ArrayList<LogEventResult.LogEvent> logEvents = new ArrayList<>();
|
||||
for (ApiResult image : uploads) {
|
||||
for (CustomApiResult image : uploads) {
|
||||
logEvents.add(new LogEventResult.LogEvent(
|
||||
image.getString("@pageid"),
|
||||
image.getString("@title"),
|
||||
|
|
@ -554,7 +562,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
@NonNull
|
||||
public List<Notification> getNotifications() {
|
||||
ApiResult notificationNode = null;
|
||||
CustomApiResult notificationNode = null;
|
||||
try {
|
||||
notificationNode = api.action("query")
|
||||
.param("notprop", "list")
|
||||
|
|
@ -589,9 +597,9 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
@NonNull
|
||||
public List<String> getSubCategoryList(String categoryName) {
|
||||
ApiResult apiResult = null;
|
||||
CustomApiResult apiResult = null;
|
||||
try {
|
||||
MWApi.RequestBuilder requestBuilder = api.action("query")
|
||||
CustomMwApi.RequestBuilder requestBuilder = api.action("query")
|
||||
.param("generator", "categorymembers")
|
||||
.param("format", "xml")
|
||||
.param("gcmtype","subcat")
|
||||
|
|
@ -609,7 +617,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
ApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||
CustomApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||
if (categoryImagesNode == null
|
||||
|| categoryImagesNode.getDocument() == null
|
||||
|| categoryImagesNode.getDocument().getChildNodes() == null
|
||||
|
|
@ -630,9 +638,9 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
@NonNull
|
||||
public List<String> getParentCategoryList(String categoryName) {
|
||||
ApiResult apiResult = null;
|
||||
CustomApiResult apiResult = null;
|
||||
try {
|
||||
MWApi.RequestBuilder requestBuilder = api.action("query")
|
||||
CustomMwApi.RequestBuilder requestBuilder = api.action("query")
|
||||
.param("generator", "categories")
|
||||
.param("format", "xml")
|
||||
.param("titles", categoryName)
|
||||
|
|
@ -649,7 +657,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
ApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||
CustomApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||
if (categoryImagesNode == null
|
||||
|| categoryImagesNode.getDocument() == null
|
||||
|| categoryImagesNode.getDocument().getChildNodes() == null
|
||||
|
|
@ -672,9 +680,9 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
@NonNull
|
||||
public List<Media> getCategoryImages(String categoryName) {
|
||||
ApiResult apiResult = null;
|
||||
CustomApiResult apiResult = null;
|
||||
try {
|
||||
MWApi.RequestBuilder requestBuilder = api.action("query")
|
||||
CustomMwApi.RequestBuilder requestBuilder = api.action("query")
|
||||
.param("generator", "categorymembers")
|
||||
.param("format", "xml")
|
||||
.param("gcmtype", "file")
|
||||
|
|
@ -699,7 +707,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
ApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||
CustomApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||
if (categoryImagesNode == null
|
||||
|| categoryImagesNode.getDocument() == null
|
||||
|| categoryImagesNode.getDocument().getChildNodes() == null
|
||||
|
|
@ -727,7 +735,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
@NonNull
|
||||
public List<Media> searchImages(String query, int offset) {
|
||||
List<ApiResult> imageNodes = null;
|
||||
List<CustomApiResult> imageNodes = null;
|
||||
try {
|
||||
imageNodes = api.action("query")
|
||||
.param("format", "xml")
|
||||
|
|
@ -748,7 +756,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
}
|
||||
|
||||
List<Media> images = new ArrayList<>();
|
||||
for (ApiResult imageNode : imageNodes) {
|
||||
for (CustomApiResult imageNode : imageNodes) {
|
||||
String imgName = imageNode.getDocument().getTextContent();
|
||||
images.add(new Media(imgName));
|
||||
}
|
||||
|
|
@ -765,7 +773,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
@Override
|
||||
@NonNull
|
||||
public List<String> searchCategory(String query, int offset) {
|
||||
List<ApiResult> categoryNodes = null;
|
||||
List<CustomApiResult> categoryNodes = null;
|
||||
try {
|
||||
categoryNodes = api.action("query")
|
||||
.param("format", "xml")
|
||||
|
|
@ -786,7 +794,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
}
|
||||
|
||||
List<String> categories = new ArrayList<>();
|
||||
for (ApiResult categoryNode : categoryNodes) {
|
||||
for (CustomApiResult categoryNode : categoryNodes) {
|
||||
String catName = categoryNode.getDocument().getTextContent();
|
||||
categories.add(catName);
|
||||
}
|
||||
|
|
@ -858,11 +866,11 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
long dataLength,
|
||||
String pageContents,
|
||||
String editSummary,
|
||||
final ProgressListener progressListener,
|
||||
Uri fileUri,
|
||||
Uri contentProviderUri) throws IOException {
|
||||
Uri contentProviderUri,
|
||||
final ProgressListener progressListener) throws IOException {
|
||||
|
||||
ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, progressListener::onProgress);
|
||||
CustomApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, getCentralAuthToken(), getEditToken(), progressListener::onProgress);
|
||||
|
||||
Log.e("WTF", "Result: " + result.toString());
|
||||
|
||||
|
|
@ -910,7 +918,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
public boolean isUserBlockedFromCommons() {
|
||||
boolean userBlocked = false;
|
||||
try {
|
||||
ApiResult result = api.action("query")
|
||||
CustomApiResult result = api.action("query")
|
||||
.param("action", "query")
|
||||
.param("format", "xml")
|
||||
.param("meta", "userinfo")
|
||||
|
|
|
|||
123
app/src/main/java/fr/free/nrw/commons/mwapi/CustomApiResult.java
Normal file
123
app/src/main/java/fr/free/nrw/commons/mwapi/CustomApiResult.java
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerException;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import in.yuvi.http.fluent.Http;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class CustomApiResult {
|
||||
private Node doc;
|
||||
private XPath evaluator;
|
||||
|
||||
CustomApiResult(Node doc) {
|
||||
this.doc = doc;
|
||||
this.evaluator = XPathFactory.newInstance().newXPath();
|
||||
}
|
||||
|
||||
static CustomApiResult fromRequestBuilder(Http.HttpRequestBuilder builder, HttpClient client) throws IOException {
|
||||
|
||||
Timber.d("API request is %s", builder.toString());
|
||||
Timber.d("API params are %s", client.getParams());
|
||||
|
||||
try {
|
||||
DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
|
||||
Document doc = docBuilder.parse(builder.use(client).charset("utf-8").data("format", "xml").asResponse().getEntity().getContent());
|
||||
printStringFromDocument(doc);
|
||||
return new CustomApiResult(doc);
|
||||
} catch (ParserConfigurationException e) {
|
||||
// I don't know wtf I can do about this on...
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalStateException e) {
|
||||
// So, this should never actually happen - since we assume MediaWiki always generates valid json
|
||||
// So the only thing causing this would be a network truncation
|
||||
// Sooo... I can throw IOError
|
||||
// Thanks Java, for making me spend significant time on shit that happens once in a bluemoon
|
||||
// I surely am writing Nuclear Submarine controller code
|
||||
throw new IOError(e);
|
||||
} catch (SAXException e) {
|
||||
// See Rant above
|
||||
throw new IOError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void printStringFromDocument(Document doc)
|
||||
{
|
||||
try
|
||||
{
|
||||
DOMSource domSource = new DOMSource(doc);
|
||||
StringWriter writer = new StringWriter();
|
||||
StreamResult result = new StreamResult(writer);
|
||||
TransformerFactory tf = TransformerFactory.newInstance();
|
||||
Transformer transformer = tf.newTransformer();
|
||||
transformer.transform(domSource, result);
|
||||
Timber.d("API response is\n %s", writer.toString());
|
||||
}
|
||||
catch(TransformerException ex)
|
||||
{
|
||||
Timber.d("Error occurred in transforming", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public Node getDocument() {
|
||||
return doc;
|
||||
}
|
||||
|
||||
public ArrayList<CustomApiResult> getNodes(String xpath) {
|
||||
try {
|
||||
ArrayList<CustomApiResult> results = new ArrayList<CustomApiResult>();
|
||||
NodeList nodes = (NodeList) evaluator.evaluate(xpath, doc, XPathConstants.NODESET);
|
||||
for(int i = 0; i < nodes.getLength(); i++) {
|
||||
results.add(new CustomApiResult(nodes.item(i)));
|
||||
}
|
||||
return results;
|
||||
} catch (XPathExpressionException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
public CustomApiResult getNode(String xpath) {
|
||||
try {
|
||||
return new CustomApiResult((Node) evaluator.evaluate(xpath, doc, XPathConstants.NODE));
|
||||
} catch (XPathExpressionException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Double getNumber(String xpath) {
|
||||
try {
|
||||
return (Double) evaluator.evaluate(xpath, doc, XPathConstants.NUMBER);
|
||||
} catch (XPathExpressionException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getString(String xpath) {
|
||||
try {
|
||||
return (String) evaluator.evaluate(xpath, doc, XPathConstants.STRING);
|
||||
} catch (XPathExpressionException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
183
app/src/main/java/fr/free/nrw/commons/mwapi/CustomMwApi.java
Normal file
183
app/src/main/java/fr/free/nrw/commons/mwapi/CustomMwApi.java
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
import org.apache.http.cookie.Cookie;
|
||||
import org.apache.http.impl.client.AbstractHttpClient;
|
||||
import org.apache.http.impl.cookie.BasicClientCookie;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import in.yuvi.http.fluent.Http;
|
||||
import in.yuvi.http.fluent.ProgressListener;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class CustomMwApi {
|
||||
public class RequestBuilder {
|
||||
private HashMap<String, Object> params;
|
||||
private CustomMwApi api;
|
||||
|
||||
RequestBuilder(CustomMwApi api) {
|
||||
params = new HashMap<String, Object>();
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
public RequestBuilder param(String key, Object value) {
|
||||
params.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public CustomApiResult get() throws IOException {
|
||||
return api.makeRequest("GET", params);
|
||||
}
|
||||
|
||||
public CustomApiResult post() throws IOException {
|
||||
return api.makeRequest("POST", params);
|
||||
}
|
||||
}
|
||||
|
||||
private AbstractHttpClient client;
|
||||
private String apiURL;
|
||||
public boolean isLoggedIn;
|
||||
private String authCookie = null;
|
||||
private String userName = null;
|
||||
private String userID = null;
|
||||
|
||||
public CustomMwApi(String apiURL, AbstractHttpClient client) {
|
||||
this.apiURL = apiURL;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public RequestBuilder action(String action) {
|
||||
RequestBuilder builder = new RequestBuilder(this);
|
||||
builder.param("action", action);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public String getAuthCookie() {
|
||||
if(authCookie == null){
|
||||
authCookie = "";
|
||||
List<Cookie> cookies = client.getCookieStore().getCookies();
|
||||
for(Cookie cookie: cookies) {
|
||||
authCookie += cookie.getName() + "=" + cookie.getValue() + ";";
|
||||
}
|
||||
}
|
||||
return authCookie;
|
||||
}
|
||||
|
||||
public void setAuthCookie(String authCookie) {
|
||||
this.authCookie = authCookie;
|
||||
this.isLoggedIn = true;
|
||||
String[] cookies = authCookie.split(";");
|
||||
String domain;
|
||||
try {
|
||||
domain = new URL(apiURL).getHost();
|
||||
} catch (MalformedURLException e) {
|
||||
// Mighty well better not happen!
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
// This works because I know which cookies are going to be set by MediaWiki, and they don't contain a = or ; in them :D
|
||||
for(String cookie: cookies) {
|
||||
String[] parts = cookie.split("=");
|
||||
BasicClientCookie c = new BasicClientCookie(parts[0], parts[1]);
|
||||
c.setDomain(domain);
|
||||
client.getCookieStore().addCookie(c);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAllCookies() {
|
||||
client.getCookieStore().clear();
|
||||
}
|
||||
|
||||
public boolean validateLogin() throws IOException {
|
||||
CustomApiResult userMeta = this.action("query").param("meta", "userinfo").get();
|
||||
this.userID = userMeta.getString("/api/query/userinfo/@id");
|
||||
this.userName = userMeta.getString("/api/query/userinfo/@name");
|
||||
Timber.d("User id is %s and user name is %s", userID, userName);
|
||||
return !userID.equals("0");
|
||||
}
|
||||
|
||||
public String getUserID() throws IOException {
|
||||
if(this.userID == null || this.userID.equals("0")) {
|
||||
this.validateLogin();
|
||||
}
|
||||
return userID;
|
||||
}
|
||||
|
||||
public String getUserName() throws IOException {
|
||||
if(this.userID == null || this.userID.equals("0")) {
|
||||
this.validateLogin();
|
||||
}
|
||||
return userName;
|
||||
}
|
||||
|
||||
public String login(String username, String password) throws IOException {
|
||||
CustomApiResult tokenData = this.action("login").param("lgname", username).param("lgpassword", password).post();
|
||||
String result = tokenData.getString("/api/login/@result");
|
||||
if (result.equals("NeedToken")) {
|
||||
String token = tokenData.getString("/api/login/@token");
|
||||
CustomApiResult confirmData = this.action("login").param("lgname", username).param("lgpassword", password).param("lgtoken", token).post();
|
||||
String finalResult = confirmData.getString("/api/login/@result");
|
||||
if(finalResult.equals("Success")) {
|
||||
isLoggedIn = true;
|
||||
}
|
||||
return finalResult;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public CustomApiResult upload(String filename, InputStream file, long length, String text, String comment, String centralAuthToken, String token) throws IOException {
|
||||
return this.upload(filename, file, length, text, comment,centralAuthToken, token, null);
|
||||
}
|
||||
|
||||
public CustomApiResult upload(String filename, InputStream file, String text, String comment, String centralAuthToken, String token) throws IOException {
|
||||
return this.upload(filename, file, -1, text, comment,centralAuthToken, token, null);
|
||||
}
|
||||
|
||||
public CustomApiResult upload(String filename, InputStream file, long length, String text, String comment, String centralAuthToken, String token, ProgressListener uploadProgressListener) throws IOException {
|
||||
Timber.d("Token being used is %s", token);
|
||||
|
||||
Http.HttpRequestBuilder builder = Http.multipart(apiURL)
|
||||
.data("action", "upload")
|
||||
.data("token", token)
|
||||
.data("centralauthtoken", centralAuthToken)
|
||||
.data("text", text)
|
||||
.data("ignorewarnings", "1")
|
||||
.data("comment", comment)
|
||||
.data("filename", filename)
|
||||
.sendProgressListener(uploadProgressListener);
|
||||
if(length != -1) {
|
||||
builder.file("file", filename, file, length);
|
||||
} else {
|
||||
builder.file("file", filename, file);
|
||||
}
|
||||
|
||||
Timber.d("Final cookies are %s", client.getCookieStore().getCookies().toString());
|
||||
|
||||
return CustomApiResult.fromRequestBuilder(builder, client);
|
||||
}
|
||||
|
||||
public void logout() throws IOException {
|
||||
// I should be doing more validation here, but meh
|
||||
isLoggedIn = false;
|
||||
this.action("logout").post();
|
||||
}
|
||||
|
||||
private CustomApiResult makeRequest(String method, HashMap<String, Object> params) throws IOException {
|
||||
Http.HttpRequestBuilder builder;
|
||||
if (method.equals("POST")) {
|
||||
builder = Http.post(apiURL);
|
||||
} else {
|
||||
builder = Http.get(apiURL);
|
||||
}
|
||||
builder.data(params);
|
||||
return CustomApiResult.fromRequestBuilder(builder, client);
|
||||
}
|
||||
}
|
||||
;
|
||||
|
|
@ -55,7 +55,7 @@ public interface MediaWikiApi {
|
|||
List<String> searchCategory(String title, int offset);
|
||||
|
||||
@NonNull
|
||||
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener, Uri fileUri, Uri contentProviderUri) throws IOException;
|
||||
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, Uri fileUri, Uri contentProviderUri, ProgressListener progressListener) throws IOException;
|
||||
|
||||
@Nullable
|
||||
String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException;
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
|||
View navHeaderView = navigationView.getHeaderView(0);
|
||||
TextView username = navHeaderView.findViewById(R.id.username);
|
||||
AccountManager accountManager = AccountManager.get(this);
|
||||
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.ACCOUNT_TYPE);
|
||||
Account[] allAccounts = accountManager.getAccountsByType(BuildConfig.ACCOUNT_TYPE);
|
||||
if (allAccounts.length != 0) {
|
||||
username.setText(allAccounts[0].name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -351,9 +351,7 @@ public class ShareActivity
|
|||
|
||||
if (Intent.ACTION_SEND.equals(intent.getAction())) {
|
||||
mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
|
||||
contentProviderUri = mediaUri;
|
||||
|
||||
mediaUri = ContributionUtils.saveFileBeingUploadedTemporarily(this, mediaUri);
|
||||
|
||||
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
||||
|
|
@ -361,7 +359,10 @@ public class ShareActivity
|
|||
} else {
|
||||
source = Contribution.SOURCE_EXTERNAL;
|
||||
}
|
||||
if (intent.hasExtra("isDirectUpload")) {
|
||||
|
||||
boolean isDirectUpload = intent.getBooleanExtra("isDirectUpload", false);
|
||||
|
||||
if (isDirectUpload) {
|
||||
Timber.d("This was initiated by a direct upload from Nearby");
|
||||
isNearbyUpload = true;
|
||||
wikiDataEntityId = intent.getStringExtra(WIKIDATA_ENTITY_ID_PREF);
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()),
|
||||
contribution
|
||||
);
|
||||
UploadResult uploadResult = mwApi.uploadFile(filename, fileInputStream, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater, contribution.getLocalUri(), contribution.getContentProviderUri());
|
||||
UploadResult uploadResult = mwApi.uploadFile(filename, fileInputStream, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), contribution.getLocalUri(), contribution.getContentProviderUri(), notificationUpdater);
|
||||
|
||||
Timber.d("Response is %s", uploadResult.toString());
|
||||
|
||||
|
|
|
|||
|
|
@ -182,15 +182,23 @@ class ApacheHttpClientMediaWikiApiTest {
|
|||
|
||||
@Test
|
||||
fun editToken() {
|
||||
server.enqueue(MockResponse().setBody("<?xml version=\"1.0\"?><api><tokens edittoken=\"baz\" /></api>"))
|
||||
server.enqueue(MockResponse().setBody("<?xml version=\"1.0\"?><api><centralauthtoken centralauthtoken=\"abc\" /></api>"))
|
||||
server.enqueue(MockResponse().setBody("<?xml version=\"1.0\"?><api><query><tokens csrftoken=\"baz\" /></query></api>"))
|
||||
|
||||
val result = testObject.editToken
|
||||
|
||||
assertBasicRequestParameters(server, "GET").let { loginTokenRequest ->
|
||||
parseQueryParams(loginTokenRequest).let { params ->
|
||||
assertBasicRequestParameters(server, "GET").let { centralAuthTokenRequest ->
|
||||
parseQueryParams(centralAuthTokenRequest).let { params ->
|
||||
assertEquals("xml", params["format"])
|
||||
assertEquals("tokens", params["action"])
|
||||
assertEquals("edit", params["type"])
|
||||
assertEquals("centralauthtoken", params["action"])
|
||||
}
|
||||
}
|
||||
|
||||
assertBasicRequestParameters(server, "POST").let { editTokenRequest ->
|
||||
parseBody(editTokenRequest.body.readUtf8()).let { body ->
|
||||
assertEquals("query", body["action"])
|
||||
assertEquals("abc", body["centralauthtoken"])
|
||||
assertEquals("tokens", body["meta"])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue