Merge pull request #9932 from osmandapp/issue_175_osmedit_use_oauth

Issue 175 osmedit use oauth
This commit is contained in:
vshcherb 2020-10-09 14:41:08 +02:00 committed by GitHub
commit 3e8f543942
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 722 additions and 143 deletions

View file

@ -104,6 +104,9 @@ dependencies {
implementation 'com.moparisthebest:junidecode:0.1.1'
implementation 'com.vividsolutions:jts-core:1.14.0'
implementation 'com.google.openlocationcode:openlocationcode:1.0.4'
implementation ('com.github.scribejava:scribejava-apis:7.1.1') {
exclude group: "com.fasterxml.jackson.core"
}
// turn off for now
//implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0'
implementation 'net.sf.kxml:kxml2:2.1.8'

View file

@ -1,30 +1,22 @@
package net.osmand.osm.io;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import net.osmand.PlatformUtil;
import net.osmand.osm.oauth.OsmOAuthAuthorizationClient;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import java.io.*;
import java.net.*;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.zip.GZIPOutputStream;
public class NetworkUtils {
private static final Log log = PlatformUtil.getLog(NetworkUtils.class);
private static final String GPX_UPLOAD_USER_AGENT = "OsmGPXUploadAgent";
private static Proxy proxy = null;
public static String sendGetRequest(String urlText, String userNamePassword, StringBuilder responseBody){
@ -55,7 +47,6 @@ public class NetworkUtils {
responseBody.append("\n"); //$NON-NLS-1$
}
responseBody.append(s);
}
is.close();
}
@ -65,9 +56,10 @@ public class NetworkUtils {
return e.getMessage();
}
}
private static final String BOUNDARY = "CowMooCowMooCowCowCow"; //$NON-NLS-1$
public static String uploadFile(String urlText, File fileToUpload, String userNamePassword, String formName, boolean gzip, Map<String, String> additionalMapData){
public static String uploadFile(String urlText, File fileToUpload, String userNamePassword,
OsmOAuthAuthorizationClient client,
String formName, boolean gzip, Map<String, String> additionalMapData){
URL url;
try {
boolean firstPrm =!urlText.contains("?");
@ -77,34 +69,48 @@ public class NetworkUtils {
}
log.info("Start uploading file to " + urlText + " " +fileToUpload.getName());
url = new URL(urlText);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
HttpURLConnection conn;
if (client != null && client.isValidToken()){
OAuthRequest req = new OAuthRequest(Verb.POST, urlText);
client.getService().signRequest(client.getAccessToken(), req);
req.addHeader("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
try {
Response r = client.getHttpClient().execute(GPX_UPLOAD_USER_AGENT, req.getHeaders(), req.getVerb(),
req.getCompleteUrl(), fileToUpload);
if (r.getCode() != 200) {
return r.getBody();
}
return null;
} catch (InterruptedException e) {
log.error(e);
} catch (ExecutionException e) {
log.error(e);
}
return null;
}
else {
conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
if(userNamePassword != null) {
conn.setRequestProperty("Authorization", "Basic " + Base64.encode(userNamePassword)); //$NON-NLS-1$ //$NON-NLS-2$
}
}
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); //$NON-NLS-1$ //$NON-NLS-2$
conn.setRequestProperty("User-Agent", "OsmAnd"); //$NON-NLS-1$ //$NON-NLS-2$
OutputStream ous = conn.getOutputStream();
// for (String key : additionalMapData.keySet()) {
// ous.write(("--" + BOUNDARY + "\r\n").getBytes());
// ous.write(("content-disposition: form-data; name=\"" + key + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
// ous.write((additionalMapData.get(key) + "\r\n").getBytes());
// }
ous.write(("--" + BOUNDARY+"\r\n").getBytes());
ous.write(("--" + BOUNDARY + "\r\n").getBytes());
String filename = fileToUpload.getName();
if(gzip){
filename+=".gz";
if (gzip) {
filename += ".gz";
}
ous.write(("content-disposition: form-data; name=\""+formName+"\"; filename=\"" + filename + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
ous.write(("content-disposition: form-data; name=\"" + formName + "\"; filename=\"" + filename + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$
InputStream fis = new FileInputStream(fileToUpload);
BufferedInputStream bis = new BufferedInputStream(fis, 20 * 1024);
ous.flush();
if(gzip){
if (gzip) {
GZIPOutputStream gous = new GZIPOutputStream(ous, 1024);
Algorithms.streamCopy(bis, gous);
gous.flush();
@ -112,7 +118,6 @@ public class NetworkUtils {
} else {
Algorithms.streamCopy(bis, ous);
}
ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
ous.flush();
Algorithms.closeStream(bis);
@ -136,7 +141,6 @@ public class NetworkUtils {
responseBody.append("\n"); //$NON-NLS-1$
}
responseBody.append(s);
}
is.close();
}
@ -157,7 +161,6 @@ public class NetworkUtils {
proxy = null;
}
}
public static Proxy getProxy() {
return proxy;
}

View file

@ -0,0 +1,259 @@
package net.osmand.osm.oauth;
import com.github.scribejava.core.exceptions.OAuthException;
import com.github.scribejava.core.httpclient.HttpClient;
import com.github.scribejava.core.httpclient.jdk.JDKHttpClientConfig;
import com.github.scribejava.core.httpclient.jdk.JDKHttpFuture;
import com.github.scribejava.core.httpclient.multipart.MultipartPayload;
import com.github.scribejava.core.httpclient.multipart.MultipartUtils;
import com.github.scribejava.core.model.*;
import net.osmand.util.Algorithms;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class OsmAndJDKHttpClient implements HttpClient {
private static final String BOUNDARY = "CowMooCowMooCowCowCow";
private final JDKHttpClientConfig config;
public OsmAndJDKHttpClient() {
this(JDKHttpClientConfig.defaultConfig());
}
public OsmAndJDKHttpClient(JDKHttpClientConfig clientConfig) {
config = clientConfig;
}
@Override
public void close() {
}
@Override
public <T> Future<T> executeAsync(String userAgent, Map<String, String> headers, Verb httpVerb, String completeUrl,
byte[] bodyContents, OAuthAsyncRequestCallback<T> callback, OAuthRequest.ResponseConverter<T> converter) {
return doExecuteAsync(userAgent, headers, httpVerb, completeUrl, BodyType.BYTE_ARRAY, bodyContents, callback,
converter);
}
@Override
public <T> Future<T> executeAsync(String userAgent, Map<String, String> headers, Verb httpVerb, String completeUrl,
MultipartPayload bodyContents, OAuthAsyncRequestCallback<T> callback,
OAuthRequest.ResponseConverter<T> converter) {
return doExecuteAsync(userAgent, headers, httpVerb, completeUrl, BodyType.MULTIPART, bodyContents, callback,
converter);
}
@Override
public <T> Future<T> executeAsync(String userAgent, Map<String, String> headers, Verb httpVerb, String completeUrl,
String bodyContents, OAuthAsyncRequestCallback<T> callback, OAuthRequest.ResponseConverter<T> converter) {
return doExecuteAsync(userAgent, headers, httpVerb, completeUrl, BodyType.STRING, bodyContents, callback,
converter);
}
@Override
public <T> Future<T> executeAsync(String userAgent, Map<String, String> headers, Verb httpVerb, String completeUrl,
File bodyContents, OAuthAsyncRequestCallback<T> callback, OAuthRequest.ResponseConverter<T> converter) {
return doExecuteAsync(userAgent, headers, httpVerb, completeUrl, BodyType.STREAM, bodyContents, callback,
converter);
}
private <T> Future<T> doExecuteAsync(String userAgent, Map<String, String> headers, Verb httpVerb,
String completeUrl, BodyType bodyType, Object bodyContents, OAuthAsyncRequestCallback<T> callback,
OAuthRequest.ResponseConverter<T> converter) {
try {
final Response response = doExecute(userAgent, headers, httpVerb, completeUrl, bodyType, bodyContents);
@SuppressWarnings("unchecked") final T t = converter == null ? (T) response : converter.convert(response);
if (callback != null) {
callback.onCompleted(t);
}
return new JDKHttpFuture<>(t);
} catch (IOException | RuntimeException e) {
if (callback != null) {
callback.onThrowable(e);
}
return new JDKHttpFuture<>(e);
}
}
@Override
public Response execute(String userAgent, Map<String, String> headers, Verb httpVerb, String completeUrl,
byte[] bodyContents) throws InterruptedException, ExecutionException, IOException {
return doExecute(userAgent, headers, httpVerb, completeUrl, BodyType.BYTE_ARRAY, bodyContents);
}
@Override
public Response execute(String userAgent, Map<String, String> headers, Verb httpVerb, String completeUrl,
MultipartPayload multipartPayloads) throws InterruptedException, ExecutionException, IOException {
return doExecute(userAgent, headers, httpVerb, completeUrl, BodyType.MULTIPART, multipartPayloads);
}
@Override
public Response execute(String userAgent, Map<String, String> headers, Verb httpVerb, String completeUrl,
String bodyContents) throws InterruptedException, ExecutionException, IOException {
return doExecute(userAgent, headers, httpVerb, completeUrl, BodyType.STRING, bodyContents);
}
@Override
public Response execute(String userAgent, Map<String, String> headers, Verb httpVerb, String completeUrl,
File bodyContents) throws InterruptedException, ExecutionException, IOException {
return doExecute(userAgent, headers, httpVerb, completeUrl, BodyType.STREAM, bodyContents);
}
private Response doExecute(String userAgent, Map<String, String> headers, Verb httpVerb, String completeUrl,
BodyType bodyType, Object bodyContents) throws IOException {
final URL url = new URL(completeUrl);
final HttpURLConnection connection;
if (config.getProxy() == null) {
connection = (HttpURLConnection) url.openConnection();
} else {
connection = (HttpURLConnection) url.openConnection(config.getProxy());
}
connection.setInstanceFollowRedirects(config.isFollowRedirects());
connection.setRequestMethod(httpVerb.name());
if (config.getConnectTimeout() != null) {
connection.setConnectTimeout(config.getConnectTimeout());
}
if (config.getReadTimeout() != null) {
connection.setReadTimeout(config.getReadTimeout());
}
addHeaders(connection, headers, userAgent);
if (httpVerb.isPermitBody()) {
bodyType.setBody(connection, bodyContents, httpVerb.isRequiresBody());
}
try {
connection.connect();
final int responseCode = connection.getResponseCode();
return new Response(responseCode, connection.getResponseMessage(), parseHeaders(connection),
responseCode >= 200 && responseCode < 400 ? connection.getInputStream()
: connection.getErrorStream());
} catch (UnknownHostException e) {
throw new OAuthException("The IP address of a host could not be determined.", e);
}
}
private enum BodyType {
BYTE_ARRAY {
@Override
void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody) throws IOException {
addBody(connection, (byte[]) bodyContents, requiresBody);
}
},
STREAM {
@Override
void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody) throws IOException {
addBody(connection, (File) bodyContents, requiresBody);
}
},
MULTIPART {
@Override
void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody) throws IOException {
addBody(connection, (MultipartPayload) bodyContents, requiresBody);
}
},
STRING {
@Override
void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody) throws IOException {
addBody(connection, ((String) bodyContents).getBytes(), requiresBody);
}
};
abstract void setBody(HttpURLConnection connection, Object bodyContents, boolean requiresBody)
throws IOException;
}
private static Map<String, String> parseHeaders(HttpURLConnection conn) {
final Map<String, String> headers = new HashMap<>();
for (Map.Entry<String, List<String>> headerField : conn.getHeaderFields().entrySet()) {
final String key = headerField.getKey();
final String value = headerField.getValue().get(0);
if ("Content-Encoding".equalsIgnoreCase(key)) {
headers.put("Content-Encoding", value);
} else {
headers.put(key, value);
}
}
return headers;
}
private static void addHeaders(HttpURLConnection connection, Map<String, String> headers, String userAgent) {
for (Map.Entry<String, String> header : headers.entrySet()) {
connection.setRequestProperty(header.getKey(), header.getValue());
}
if (userAgent != null) {
connection.setRequestProperty(OAuthConstants.USER_AGENT_HEADER_NAME, userAgent);
}
}
private static void addBody(HttpURLConnection connection, File file, boolean requiresBody) throws IOException {
if (requiresBody) {
String filename = file.getName();
String formName = "file";
InputStream stream = new FileInputStream(file);
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); //$NON-NLS-1$ //$NON-NLS-2$
connection.setRequestProperty("User-Agent", "OsmAnd"); //$NON-NLS-1$ //$NON-NLS-2$
final OutputStream ous = connection.getOutputStream();
ous.write(("--" + BOUNDARY + "\r\n").getBytes());
ous.write(("content-disposition: form-data; name=\"" + formName + "\"; filename=\"" + filename + "\"\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
ous.write(("Content-Type: application/octet-stream\r\n\r\n").getBytes()); //$NON-NLS-1$
BufferedInputStream bis = new BufferedInputStream(stream, 20 * 1024);
ous.flush();
Algorithms.streamCopy(bis, ous);
ous.write(("\r\n--" + BOUNDARY + "--\r\n").getBytes()); //$NON-NLS-1$ //$NON-NLS-2$
ous.flush();
Algorithms.closeStream(bis);
}
}
private static void addBody(HttpURLConnection connection, byte[] content, boolean requiresBody) throws IOException {
final int contentLength = content.length;
if (requiresBody || contentLength > 0) {
connection.setDoOutput(true);
final OutputStream outputStream = prepareConnectionForBodyAndGetOutputStream(connection, contentLength);
if (contentLength > 0) {
outputStream.write(content);
}
}
}
private static void addBody(HttpURLConnection connection, MultipartPayload multipartPayload, boolean requiresBody)
throws IOException {
for (Map.Entry<String, String> header : multipartPayload.getHeaders().entrySet()) {
connection.setRequestProperty(header.getKey(), header.getValue());
}
if (requiresBody) {
final ByteArrayOutputStream os = MultipartUtils.getPayload(multipartPayload);
final int contentLength = os.size();
connection.setDoOutput(true);
final OutputStream outputStream = prepareConnectionForBodyAndGetOutputStream(connection, contentLength);
if (contentLength > 0) {
os.writeTo(outputStream);
}
}
}
private static OutputStream prepareConnectionForBodyAndGetOutputStream(HttpURLConnection connection,
int contentLength) throws IOException {
connection.setRequestProperty(CONTENT_LENGTH, String.valueOf(contentLength));
if (connection.getRequestProperty(CONTENT_TYPE) == null) {
connection.setRequestProperty(CONTENT_TYPE, DEFAULT_CONTENT_TYPE);
}
return connection.getOutputStream();
}
}

View file

@ -0,0 +1,149 @@
// License: GPL. For details, see LICENSE file.
package net.osmand.osm.oauth;
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.builder.api.DefaultApi10a;
import com.github.scribejava.core.builder.api.OAuth1SignatureType;
import com.github.scribejava.core.httpclient.jdk.JDKHttpClientConfig;
import com.github.scribejava.core.model.*;
import com.github.scribejava.core.oauth.OAuth10aService;
import net.osmand.PlatformUtil;
import org.apache.commons.logging.Log;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
/**
* An OAuth 1.0 authorization client.
*
* @since 2746
*/
public class OsmOAuthAuthorizationClient {
private OAuth1RequestToken requestToken;
private OAuth1AccessToken accessToken;
private final OAuth10aService service;
private final OsmAndJDKHttpClient httpClient;
public final static Log log = PlatformUtil.getLog(OsmOAuthAuthorizationClient.class);
public OsmOAuthAuthorizationClient(String key, String secret) {
httpClient = new OsmAndJDKHttpClient(JDKHttpClientConfig.defaultConfig());
service = new ServiceBuilder(key)
.apiSecret(secret)
.httpClient(httpClient)
.callback("osmand-oauth://example.com/oauth")
.build(new OsmApi());
}
static class OsmApi extends DefaultApi10a {
@Override
public OAuth1SignatureType getSignatureType() {
return OAuth1SignatureType.QUERY_STRING;
}
@Override
public String getRequestTokenEndpoint() {
return "https://www.openstreetmap.org/oauth/request_token";
}
@Override
public String getAccessTokenEndpoint() {
return "https://www.openstreetmap.org/oauth/access_token";
}
@Override
protected String getAuthorizationBaseUrl() {
return "https://www.openstreetmap.org/oauth/authorize";
}
}
public OsmAndJDKHttpClient getHttpClient() {
return httpClient;
}
public OAuth10aService getService() {
return service;
}
public void setAccessToken(OAuth1AccessToken accessToken) {
this.accessToken = accessToken;
}
public OAuth1AccessToken getAccessToken() {
return accessToken;
}
public Response performRequestWithoutAuth(String url, String requestMethod, String requestBody)
throws InterruptedException, ExecutionException, IOException {
Verb verb = parseRequestMethod(requestMethod);
OAuthRequest req = new OAuthRequest(verb, url);
req.setPayload(requestBody);
return service.execute(req);
}
public void performGetRequest(String url, OAuthAsyncRequestCallback<Response> callback) {
if (accessToken == null) {
throw new IllegalStateException("Access token is null");
}
OAuthRequest req = new OAuthRequest(Verb.GET, url);
service.signRequest(accessToken, req);
service.execute(req, callback);
}
public Response performRequest(String url, String method, String body)
throws InterruptedException, ExecutionException, IOException {
service.getApi().getSignatureType();
if (accessToken == null) {
throw new IllegalStateException("Access token is null");
}
Verb verbMethod = parseRequestMethod(method);
OAuthRequest req = new OAuthRequest(verbMethod, url);
req.setPayload(body);
service.signRequest(accessToken, req);
req.addHeader("Content-Type", "application/xml");
return service.execute(req);
}
public OAuth1RequestToken startOAuth() {
try {
requestToken = service.getRequestToken();
} catch (IOException e) {
log.error(e);
} catch (InterruptedException e) {
log.error(e);
} catch (ExecutionException e) {
log.error(e);
}
return requestToken;
}
public OAuth1AccessToken authorize(String oauthVerifier) {
try {
setAccessToken(service.getAccessToken(requestToken, oauthVerifier));
} catch (IOException e) {
log.error(e);
} catch (InterruptedException e) {
log.error(e);
} catch (ExecutionException e) {
log.error(e);
}
return accessToken;
}
public boolean isValidToken() {
return !(accessToken == null);
}
private Verb parseRequestMethod(String method) {
Verb m = Verb.GET;
if (method.equals("POST")) {
m = Verb.POST;
}
if (method.equals("PUT")) {
m = Verb.PUT;
}
if (method.equals("DELETE")) {
m = Verb.DELETE;
}
return m;
}
}

View file

@ -479,7 +479,16 @@
<activity android:name="net.osmand.plus.activities.SettingsNavigationActivity" android:configChanges="keyboardHidden|orientation" />
<activity android:name="net.osmand.plus.monitoring.SettingsMonitoringActivity" android:configChanges="keyboardHidden|orientation" />
<activity android:name="net.osmand.plus.osmedit.SettingsOsmEditingActivity" android:configChanges="keyboardHidden|orientation" />
<activity android:name="net.osmand.plus.osmedit.SettingsOsmEditingActivity"
android:launchMode="singleInstance"
android:configChanges="keyboardHidden|orientation">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="osmand-oauth" />
</intent-filter>
</activity>
<activity android:name="net.osmand.plus.development.SettingsDevelopmentActivity" android:configChanges="keyboardHidden|orientation" />
<activity android:name="net.osmand.plus.audionotes.SettingsAudioVideoActivity" android:configChanges="keyboardHidden|orientation" />
<activity android:name="net.osmand.access.SettingsAccessibilityActivity" android:configChanges="keyboardHidden|orientation" />

View file

@ -227,9 +227,13 @@ android {
buildTypes {
debug {
buildConfigField "String", "OSM_OAUTH_CONSUMER_KEY", "\"Ti2qq3fo4i4Wmuox3SiWRIGq3obZisBHnxmcM05y\""
buildConfigField "String", "OSM_OAUTH_CONSUMER_SECRET", "\"lxulb3HYoMmd2cC4xxNe1dyfRMAY8dS0eNihJ0DM\""
signingConfig signingConfigs.development
}
release {
buildConfigField "String", "OSM_OAUTH_CONSUMER_KEY", "\"Ti2qq3fo4i4Wmuox3SiWRIGq3obZisBHnxmcM05y\""
buildConfigField "String", "OSM_OAUTH_CONSUMER_SECRET", "\"lxulb3HYoMmd2cC4xxNe1dyfRMAY8dS0eNihJ0DM\""
if (gradle.startParameter.taskNames.toString().contains("huawei")) {
signingConfig signingConfigs.publishingHuawei
} else {
@ -532,6 +536,9 @@ dependencies {
implementation ("com.github.HITGIF:TextFieldBoxes:1.4.5"){
exclude group: 'com.android.support'
}
implementation('com.github.scribejava:scribejava-apis:7.1.1'){
exclude group: "com.fasterxml.jackson.core"
}
implementation 'com.jaredrummler:colorpicker:1.1.0'
freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300'

View file

@ -11,6 +11,10 @@
Thx - Hardy
-->
<string name="osm_edit_logout_success">Logout successful</string>
<string name="clear_osm_token">Clear OpenStreetMap OAuth token</string>
<string name="perform_oauth_authorization">Log in via OAuth</string>
<string name="perform_oauth_authorization_description">Perform an OAuth Login to use osmedit features</string>
<string name="use_native_pt_desc">Switch to Java (safe) Public Transport routing calculation</string>
<string name="use_native_pt">Native Public Transport development</string>
<string name="use_fast_recalculation_desc">Recalculates only the initial part of the route. Can be used for long trips.</string>

View file

@ -5,6 +5,7 @@ import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.TrafficStats;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.AsyncTask.Status;
@ -58,8 +59,8 @@ public class DownloadIndexesThread {
private ConcurrentLinkedQueue<IndexItem> indexItemDownloading = new ConcurrentLinkedQueue<IndexItem>();
private IndexItem currentDownloadingItem = null;
private int currentDownloadingItemProgress = 0;
private DownloadResources indexes;
private static final int THREAD_ID = 10103;
public interface DownloadEvents {
@ -336,6 +337,7 @@ public class DownloadIndexesThread {
@Override
protected DownloadResources doInBackground(Void... params) {
TrafficStats.setThreadStatsTag(THREAD_ID);
DownloadResources result = null;
DownloadOsmandIndexesHelper.IndexFileList indexFileList = DownloadOsmandIndexesHelper.getIndexesList(ctx);
if (indexFileList != null) {

View file

@ -2,10 +2,8 @@ package net.osmand.plus.osmedit;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import net.osmand.data.PointDescription;
import net.osmand.osm.PoiType;
import net.osmand.plus.OsmandPlugin;
@ -14,6 +12,7 @@ import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.mapcontextmenu.MenuController;
import net.osmand.plus.osmedit.OsmPoint.Action;
import net.osmand.plus.osmedit.dialogs.SendPoiDialogFragment;
import net.osmand.plus.osmedit.oauth.OsmOAuthAuthorizationAdapter;
import net.osmand.plus.render.RenderingIcons;
import net.osmand.util.Algorithms;
@ -39,11 +38,18 @@ public class EditPOIMenuController extends MenuController {
public void buttonPressed() {
MapActivity activity = getMapActivity();
if (plugin != null && activity != null) {
OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(activity.getMyApplication());
if (client.isValidToken()){
new SendPoiDialogFragment.SimpleProgressDialogPoiUploader(activity).
showProgressDialog(new OsmPoint[] { getOsmPoint() }, false, false);
}
else {
SendPoiDialogFragment sendPoiDialogFragment =
SendPoiDialogFragment.createInstance(new OsmPoint[]{getOsmPoint()}, SendPoiDialogFragment.PoiUploaderType.SIMPLE);
sendPoiDialogFragment.show(activity.getSupportFragmentManager(), SendPoiDialogFragment.TAG);
}
}
}
};
leftTitleButtonController.caption = mapActivity.getString(R.string.shared_string_upload);
leftTitleButtonController.startIconId = R.drawable.ic_action_export;

View file

@ -2,7 +2,8 @@ package net.osmand.plus.osmedit;
import android.util.Xml;
import android.widget.Toast;
import com.github.scribejava.core.model.Response;
import gnu.trove.list.array.TLongArrayList;
import net.osmand.NativeLibrary;
import net.osmand.PlatformUtil;
import net.osmand.data.Amenity;
@ -17,38 +18,29 @@ import net.osmand.osm.edit.Entity.EntityType;
import net.osmand.osm.edit.EntityInfo;
import net.osmand.osm.edit.Node;
import net.osmand.osm.edit.Way;
import net.osmand.osm.io.Base64;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.osm.io.OsmBaseStorage;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import net.osmand.plus.osmedit.oauth.OsmOAuthAuthorizationAdapter;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import gnu.trove.list.array.TLongArrayList;
import java.util.concurrent.ExecutionException;
public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil {
@ -99,12 +91,16 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil {
private final static String URL_TO_UPLOAD_GPX = getSiteApi() + "api/0.6/gpx/create";
public String uploadGPXFile(String tagstring, String description, String visibility, File f) {
OsmOAuthAuthorizationAdapter adapter = new OsmOAuthAuthorizationAdapter(ctx);
String url = URL_TO_UPLOAD_GPX;
Map<String, String> additionalData = new LinkedHashMap<String, String>();
additionalData.put("description", description);
additionalData.put("tags", tagstring);
additionalData.put("visibility", visibility);
return NetworkUtils.uploadFile(url, f, settings.USER_NAME.get() + ":" + settings.USER_PASSWORD.get(), "file",
return NetworkUtils.uploadFile(url, f,
settings.USER_NAME.get() + ":" + settings.USER_PASSWORD.get(),
adapter.getClient(),
"file",
true, additionalData);
}
@ -112,53 +108,15 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil {
boolean doAuthenticate) {
log.info("Sending request " + url); //$NON-NLS-1$
try {
HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url);
connection.setConnectTimeout(15000);
connection.setRequestMethod(requestMethod);
connection.setRequestProperty("User-Agent", Version.getFullVersion(ctx)); //$NON-NLS-1$
StringBuilder responseBody = new StringBuilder();
if (doAuthenticate) {
String token = settings.USER_NAME.get() + ":" + settings.USER_PASSWORD.get(); //$NON-NLS-1$
connection.addRequestProperty("Authorization", "Basic " + Base64.encode(token.getBytes("UTF-8"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (doAuthenticate){
OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx);
Response response = client.performRequest(url,requestMethod,requestBody);
return response.getBody();
}
connection.setDoInput(true);
if (requestMethod.equals("PUT") || requestMethod.equals("POST") || requestMethod.equals("DELETE")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
connection.setDoOutput(true);
connection.setRequestProperty("Content-type", "text/xml"); //$NON-NLS-1$ //$NON-NLS-2$
OutputStream out = connection.getOutputStream();
if (requestBody != null) {
BufferedWriter bwr = new BufferedWriter(new OutputStreamWriter(out, "UTF-8"), 1024); //$NON-NLS-1$
bwr.write(requestBody);
bwr.flush();
}
out.close();
}
connection.connect();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
String msg = userOperation
+ " " + ctx.getString(R.string.failed_op) + " : " + connection.getResponseMessage(); //$NON-NLS-1$//$NON-NLS-2$
log.error(msg);
showWarning(msg);
} else {
log.info("Response : " + connection.getResponseMessage()); //$NON-NLS-1$
// populate return fields.
responseBody.setLength(0);
InputStream i = connection.getInputStream();
if (i != null) {
BufferedReader in = new BufferedReader(new InputStreamReader(i, "UTF-8"), 256); //$NON-NLS-1$
String s;
boolean f = true;
while ((s = in.readLine()) != null) {
if (!f) {
responseBody.append("\n"); //$NON-NLS-1$
} else {
f = false;
}
responseBody.append(s);
}
}
return responseBody.toString();
else {
OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(ctx);
Response response = client.performRequestWithoutAuth(url,requestMethod,requestBody);
return response.getBody();
}
} catch (NullPointerException e) {
// that's tricky case why NPE is thrown to fix that problem httpClient could be used
@ -173,6 +131,14 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil {
log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$
showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
+ ": " + ctx.getResources().getString(R.string.shared_string_io_error), userOperation));
} catch (InterruptedException e) {
log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$
showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
+ ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation));
} catch (ExecutionException e) {
log.error(userOperation + " " + ctx.getString(R.string.failed_op), e); //$NON-NLS-1$
showWarning(MessageFormat.format(ctx.getResources().getString(R.string.shared_string_action_template)
+ ": " + ctx.getResources().getString(R.string.shared_string_unexpected_error), userOperation));
}
return null;
@ -208,10 +174,14 @@ public class OpenstreetmapRemoteUtil implements OpenstreetmapUtil {
}
String response = sendRequest(
getSiteApi() + "api/0.6/changeset/create/", "PUT", writer.getBuffer().toString(), ctx.getString(R.string.opening_changeset), true); //$NON-NLS-1$ //$NON-NLS-2$
try {
if (response != null && response.length() > 0) {
log.debug(response);
id = Long.parseLong(response);
}
} catch (Exception e) {
log.error(e);
}
return id;
}

View file

@ -5,12 +5,12 @@ import net.osmand.PlatformUtil;
import net.osmand.osm.io.Base64;
import net.osmand.osm.io.NetworkUtils;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import net.osmand.plus.osmedit.OsmPoint.Action;
import net.osmand.plus.osmedit.oauth.OsmOAuthAuthorizationAdapter;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import java.io.FileNotFoundException;
@ -109,6 +109,7 @@ public class OsmBugsRemoteUtil implements OsmBugsUtil {
private OsmBugResult editingPOI(String url, String requestMethod, String userOperation,
boolean anonymous) {
OsmOAuthAuthorizationAdapter client = new OsmOAuthAuthorizationAdapter(app);
OsmBugResult r = new OsmBugResult();
try {
HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url);
@ -118,9 +119,13 @@ public class OsmBugsRemoteUtil implements OsmBugsUtil {
connection.setRequestProperty("User-Agent", Version.getFullVersion(app)); //$NON-NLS-1$
if (!anonymous) {
if (client.isValidToken()) {
connection.addRequestProperty("Authorization", "OAuth " + client.getClient().getAccessToken().getToken());
} else {
String token = settings.USER_NAME.get() + ":" + settings.USER_PASSWORD.get(); //$NON-NLS-1$
connection.addRequestProperty("Authorization", "Basic " + Base64.encode(token.getBytes("UTF-8"))); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
connection.setDoInput(true);
connection.connect();

View file

@ -13,11 +13,9 @@ import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import net.osmand.AndroidUtils;
import net.osmand.PlatformUtil;
import net.osmand.data.Amenity;
@ -25,7 +23,7 @@ import net.osmand.data.MapObject;
import net.osmand.data.TransportStop;
import net.osmand.osm.PoiType;
import net.osmand.osm.edit.Entity;
import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.*;
import net.osmand.plus.ContextMenuAdapter.ItemClickListener;
import net.osmand.plus.ContextMenuItem;
import net.osmand.plus.OsmandApplication;
@ -44,22 +42,16 @@ import net.osmand.plus.myplaces.AvailableGPXFragment.GpxInfo;
import net.osmand.plus.myplaces.FavoritesActivity;
import net.osmand.plus.osmedit.OsmPoint.Action;
import net.osmand.plus.quickaction.QuickActionType;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.settings.fragments.BaseSettingsFragment;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.util.Algorithms;
import org.apache.commons.logging.Log;
import java.util.ArrayList;
import java.util.List;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_CREATE_POI;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_MODIFY_OSM_CHANGE;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_MODIFY_OSM_NOTE;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_MODIFY_POI;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.MAP_CONTEXT_MENU_OPEN_OSM_NOTE;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.OSM_EDITS;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.OSM_NOTES;
import static net.osmand.aidlapi.OsmAndCustomizationConstants.*;
import static net.osmand.plus.ContextMenuAdapter.makeDeleteAction;
@ -465,7 +457,8 @@ public class OsmEditingPlugin extends OsmandPlugin {
public boolean sendGPXFiles(final Activity la, AvailableGPXFragment f, final GpxInfo... info) {
String name = settings.USER_NAME.get();
String pwd = settings.USER_PASSWORD.get();
if (Algorithms.isEmpty(name) || Algorithms.isEmpty(pwd)) {
String authToken = settings.USER_ACCESS_TOKEN.get();
if ((Algorithms.isEmpty(name) || Algorithms.isEmpty(pwd)) && Algorithms.isEmpty(authToken)) {
Toast.makeText(la, R.string.validate_gpx_upload_name_pwd, Toast.LENGTH_LONG).show();
return false;
}

View file

@ -3,8 +3,10 @@ package net.osmand.plus.osmedit;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.preference.CheckBoxPreference;
import android.preference.DialogPreference;
import android.preference.Preference;
@ -12,22 +14,40 @@ import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import net.osmand.plus.settings.backend.OsmAndAppCustomization;
import com.github.scribejava.core.model.OAuthAsyncRequestCallback;
import com.github.scribejava.core.model.Response;
import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.activities.SettingsBaseActivity;
import net.osmand.plus.osmedit.oauth.OsmOAuthAuthorizationAdapter;
import net.osmand.plus.settings.backend.OsmAndAppCustomization;
import org.apache.commons.logging.Log;
import java.io.IOException;
public class SettingsOsmEditingActivity extends SettingsBaseActivity {
private OsmOAuthAuthorizationAdapter client;
private static final Log log = PlatformUtil.getLog(SettingsOsmEditingActivity.class);
@Override
public void onCreate(Bundle savedInstanceState) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
((OsmandApplication) getApplication()).applyTheme(this);
super.onCreate(savedInstanceState);
client = new OsmOAuthAuthorizationAdapter(getMyApplication());
getToolbar().setTitle(R.string.osm_settings);
@SuppressWarnings("deprecation")
PreferenceScreen grp = getPreferenceScreen();
@ -39,7 +59,7 @@ public class SettingsOsmEditingActivity extends SettingsBaseActivity {
R.string.offline_edition, R.string.offline_edition_descr);
grp.addPreference(poiEdit);
Preference pref = new Preference(this);
final Preference pref = new Preference(this);
pref.setTitle(R.string.local_openstreetmap_settings);
pref.setSummary(R.string.local_openstreetmap_settings_descr);
pref.setKey("local_openstreetmap_points");
@ -56,6 +76,44 @@ public class SettingsOsmEditingActivity extends SettingsBaseActivity {
}
});
grp.addPreference(pref);
final Preference prefOAuth = new Preference(this);
if (client.isValidToken()){
prefOAuth.setTitle(R.string.osm_authorization_success);
prefOAuth.setSummary(R.string.osm_authorization_success);
prefOAuth.setKey("local_openstreetmap_oauth_success");
final Preference prefClearToken = new Preference(this);
prefClearToken.setTitle(R.string.shared_string_logoff);
prefClearToken.setSummary(R.string.clear_osm_token);
prefClearToken.setKey("local_openstreetmap_oauth_clear");
prefClearToken.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
settings.USER_ACCESS_TOKEN.set("");
settings.USER_ACCESS_TOKEN_SECRET.set("");
client.resetToken();
Toast.makeText(SettingsOsmEditingActivity.this, R.string.osm_edit_logout_success, Toast.LENGTH_SHORT).show();
finish();
startActivity(getIntent());
return true;
}
});
grp.addPreference(prefClearToken);
}
else {
prefOAuth.setTitle(R.string.perform_oauth_authorization);
prefOAuth.setSummary(R.string.perform_oauth_authorization_description);
prefOAuth.setKey("local_openstreetmap_oauth_login");
prefOAuth.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
ViewGroup preferenceView = (ViewGroup)getListView().getChildAt(preference.getOrder());
client.startOAuth(preferenceView);
return true;
}
});
}
grp.addPreference(prefOAuth);
}
public class OsmLoginDataDialogPreference extends DialogPreference {
@ -116,4 +174,17 @@ public class SettingsOsmEditingActivity extends SettingsBaseActivity {
Toast.makeText(context, text, Toast.LENGTH_LONG).show();
}
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Uri uri = intent.getData();
System.out.println("URI=" + uri);
if (uri != null && uri.toString().startsWith("osmand-oauth")) {
String oauthVerifier = uri.getQueryParameter("oauth_verifier");
client.authorize(oauthVerifier);
finish();
startActivity(getIntent());
}
}
}

View file

@ -1,6 +1,7 @@
package net.osmand.plus.osmedit;
import android.content.DialogInterface;
import android.net.TrafficStats;
import android.os.AsyncTask;
import net.osmand.osm.edit.Entity;
@ -24,6 +25,7 @@ public class UploadOpenstreetmapPointAsyncTask
private OsmEditingPlugin plugin;
private final boolean closeChangeSet;
private final boolean loadAnonymous;
private static final int THREAD_ID = 10102;
public UploadOpenstreetmapPointAsyncTask(ProgressDialogFragment progress,
OsmEditsUploadListener listener,
@ -43,6 +45,8 @@ public class UploadOpenstreetmapPointAsyncTask
@Override
protected Map<OsmPoint, String> doInBackground(OsmPoint... points) {
TrafficStats.setThreadStatsTag(THREAD_ID);
Map<OsmPoint, String> loadErrorsMap = new HashMap<>();
boolean uploaded = false;

View file

@ -0,0 +1,88 @@
package net.osmand.plus.osmedit.oauth;
import android.net.TrafficStats;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import com.github.scribejava.core.model.OAuth1AccessToken;
import com.github.scribejava.core.model.OAuth1RequestToken;
import com.github.scribejava.core.model.OAuthAsyncRequestCallback;
import com.github.scribejava.core.model.Response;
import net.osmand.osm.oauth.OsmOAuthAuthorizationClient;
import net.osmand.plus.BuildConfig;
import net.osmand.plus.OsmandApplication;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
public class OsmOAuthAuthorizationAdapter {
private OsmandApplication application;
private OsmOAuthAuthorizationClient client =
new OsmOAuthAuthorizationClient(BuildConfig.OSM_OAUTH_CONSUMER_KEY, BuildConfig.OSM_OAUTH_CONSUMER_SECRET);
private static final int THREAD_ID = 10101;
public OsmOAuthAuthorizationAdapter(OsmandApplication application) {
TrafficStats.setThreadStatsTag(THREAD_ID);
this.application = application;
restoreToken();
}
public OsmOAuthAuthorizationClient getClient() {
return client;
}
public boolean isValidToken() {
return client.isValidToken();
}
public void resetToken() {
client.setAccessToken(null);
}
public void restoreToken() {
String token = application.getSettings().USER_ACCESS_TOKEN.get();
String tokenSecret = application.getSettings().USER_ACCESS_TOKEN_SECRET.get();
if (!(token.isEmpty() || tokenSecret.isEmpty())) {
client.setAccessToken(new OAuth1AccessToken(token, tokenSecret));
} else {
client.setAccessToken(null);
}
}
public void startOAuth(ViewGroup rootLayout) {
OAuth1RequestToken requestToken = client.startOAuth();
loadWebView(rootLayout, client.getService().getAuthorizationUrl(requestToken));
}
private void saveToken() {
OAuth1AccessToken accessToken = client.getAccessToken();
application.getSettings().USER_ACCESS_TOKEN.set(accessToken.getToken());
application.getSettings().USER_ACCESS_TOKEN_SECRET.set(accessToken.getTokenSecret());
}
private void loadWebView(ViewGroup root, String url) {
WebView webView = new WebView(root.getContext());
webView.requestFocus(View.FOCUS_DOWN);
webView.loadUrl(url);
root.addView(webView);
}
public void performGetRequest(String url, OAuthAsyncRequestCallback<Response> callback) {
client.performGetRequest(url, callback);
}
public Response performRequest(String url, String method, String body)
throws InterruptedException, ExecutionException, IOException {
return client.performRequest(url, method, body);
}
public Response performRequestWithoutAuth(String url, String method, String body)
throws InterruptedException, ExecutionException, IOException {
return client.performRequestWithoutAuth(url, method, body);
}
public void authorize(String oauthVerifier) {
client.authorize(oauthVerifier);
saveToken();
}
}

View file

@ -1125,6 +1125,12 @@ public class OsmandSettings {
public final OsmandPreference<String> USER_PASSWORD =
new StringPreference(this, "user_password", "").makeGlobal();
public final OsmandPreference<String> USER_ACCESS_TOKEN =
new StringPreference("user_access_token", "").makeGlobal();
public final OsmandPreference<String> USER_ACCESS_TOKEN_SECRET =
new StringPreference("user_access_token_secret", "").makeGlobal();
// this value boolean is synchronized with settings_pref.xml preference offline POI/Bugs edition
public final OsmandPreference<Boolean> OFFLINE_EDITION = new BooleanPreference(this, "offline_osm_editing", true).makeGlobal();