Merge remote-tracking branch 'refs/remotes/origin/2.8-release'

This commit is contained in:
misaochan 2018-08-06 18:36:35 +10:00
commit 23014e07c8
13 changed files with 459 additions and 85 deletions

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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")

View 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;
}
}
}

View 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);
}
}
;

View file

@ -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;

View file

@ -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);
}

View file

@ -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);

View file

@ -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());

View file

@ -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"])
}
}