diff --git a/android/app/src/main/cpp/app/organicmaps/Framework.cpp b/android/app/src/main/cpp/app/organicmaps/Framework.cpp
index fe3b5435cf..0be0e09d42 100644
--- a/android/app/src/main/cpp/app/organicmaps/Framework.cpp
+++ b/android/app/src/main/cpp/app/organicmaps/Framework.cpp
@@ -862,6 +862,13 @@ Java_app_organicmaps_Framework_nativeGetParsedAppName(JNIEnv * env, jclass)
return (appName.empty()) ? nullptr : jni::ToJavaString(env, appName);
}
+JNIEXPORT jstring JNICALL
+Java_app_organicmaps_Framework_nativeGetParsedOAuth2Code(JNIEnv * env, jclass)
+{
+ std::string const & code = frm()->GetParsedOAuth2Code();
+ return jni::ToJavaString(env, code);
+}
+
JNIEXPORT jstring JNICALL
Java_app_organicmaps_Framework_nativeGetParsedBackUrl(JNIEnv * env, jclass)
{
diff --git a/android/app/src/main/cpp/app/organicmaps/editor/OsmOAuth.cpp b/android/app/src/main/cpp/app/organicmaps/editor/OsmOAuth.cpp
index 90d320ef65..fc18cf19f8 100644
--- a/android/app/src/main/cpp/app/organicmaps/editor/OsmOAuth.cpp
+++ b/android/app/src/main/cpp/app/organicmaps/editor/OsmOAuth.cpp
@@ -34,6 +34,13 @@ bool LoadOsmUserPreferences(std::string const & oauthToken, UserPreferences & ou
extern "C"
{
+JNIEXPORT jstring JNICALL
+Java_app_organicmaps_editor_OsmOAuth_nativeGetOAuth2Url(JNIEnv * env, jclass)
+{
+ auto const auth = OsmOAuth::ServerAuth();
+ return ToJavaString(env, auth.BuildOAuth2Url());
+}
+
JNIEXPORT jstring JNICALL
Java_app_organicmaps_editor_OsmOAuth_nativeAuthWithPassword(JNIEnv * env, jclass clazz,
jstring login, jstring password)
@@ -52,6 +59,27 @@ Java_app_organicmaps_editor_OsmOAuth_nativeAuthWithPassword(JNIEnv * env, jclass
return nullptr;
}
+JNIEXPORT jstring JNICALL
+Java_app_organicmaps_editor_OsmOAuth_nativeAuthWithOAuth2Code(JNIEnv * env, jclass, jstring oauth2code)
+{
+ OsmOAuth auth = OsmOAuth::ServerAuth();
+ try
+ {
+ auto token = auth.FinishAuthorization(ToNativeString(env, oauth2code));
+ if (!token.empty())
+ {
+ auth.SetAuthToken(token);
+ return ToJavaString(env, token);
+ }
+ LOG(LWARNING, ("nativeAuthWithOAuth2Code: invalid OAuth2 code", oauth2code));
+ }
+ catch (std::exception const & ex)
+ {
+ LOG(LWARNING, ("nativeAuthWithOAuth2Code error ", ex.what()));
+ }
+ return nullptr;
+}
+
JNIEXPORT jstring JNICALL
Java_app_organicmaps_editor_OsmOAuth_nativeGetOsmUsername(JNIEnv * env, jclass, jstring oauthToken)
{
diff --git a/android/app/src/main/java/app/organicmaps/Framework.java b/android/app/src/main/java/app/organicmaps/Framework.java
index 2a076225c3..a5d65bbb96 100644
--- a/android/app/src/main/java/app/organicmaps/Framework.java
+++ b/android/app/src/main/java/app/organicmaps/Framework.java
@@ -239,6 +239,7 @@ public class Framework
public static native ParsedRoutingData nativeGetParsedRoutingData();
public static native ParsedSearchRequest nativeGetParsedSearchRequest();
public static native @Nullable String nativeGetParsedAppName();
+ public static native @Nullable String nativeGetParsedOAuth2Code();
@Nullable @Size(2)
public static native double[] nativeGetParsedCenterLatLon();
public static native @Nullable String nativeGetParsedBackUrl();
diff --git a/android/app/src/main/java/app/organicmaps/api/RequestType.java b/android/app/src/main/java/app/organicmaps/api/RequestType.java
index e90743a5bb..0634d305cf 100644
--- a/android/app/src/main/java/app/organicmaps/api/RequestType.java
+++ b/android/app/src/main/java/app/organicmaps/api/RequestType.java
@@ -6,7 +6,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.SOURCE)
-@IntDef({RequestType.INCORRECT, RequestType.MAP, RequestType.ROUTE, RequestType.SEARCH, RequestType.CROSSHAIR})
+@IntDef({RequestType.INCORRECT, RequestType.MAP, RequestType.ROUTE, RequestType.SEARCH, RequestType.CROSSHAIR, RequestType.OAUTH2})
public @interface RequestType
{
// Represents url_scheme::ParsedMapApi::UrlType from c++ part.
@@ -15,4 +15,5 @@ public @interface RequestType
public static final int ROUTE = 2;
public static final int SEARCH = 3;
public static final int CROSSHAIR = 4;
+ public static final int OAUTH2 = 5;
}
diff --git a/android/app/src/main/java/app/organicmaps/editor/OsmLoginActivity.java b/android/app/src/main/java/app/organicmaps/editor/OsmLoginActivity.java
index 35894517bf..0e15133d16 100644
--- a/android/app/src/main/java/app/organicmaps/editor/OsmLoginActivity.java
+++ b/android/app/src/main/java/app/organicmaps/editor/OsmLoginActivity.java
@@ -1,14 +1,31 @@
package app.organicmaps.editor;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import app.organicmaps.base.BaseMwmFragmentActivity;
public class OsmLoginActivity extends BaseMwmFragmentActivity
{
+ public static final String EXTRA_OAUTH2CODE = "oauth2code";
+
@Override
protected Class extends Fragment> getFragmentClass()
{
return OsmLoginFragment.class;
}
+
+ public static void OAuth2Callback(@NonNull Activity activity, String oauth2code)
+ {
+ final Intent i = new Intent(activity, OsmLoginActivity.class);
+ Bundle args = new Bundle();
+ args.putString(EXTRA_OAUTH2CODE, oauth2code);
+ args.putBoolean(ProfileActivity.EXTRA_REDIRECT_TO_PROFILE, true);
+ i.putExtras(args);
+ activity.startActivity(i);
+ }
}
diff --git a/android/app/src/main/java/app/organicmaps/editor/OsmLoginBottomFragment.java b/android/app/src/main/java/app/organicmaps/editor/OsmLoginBottomFragment.java
new file mode 100644
index 0000000000..f91e9bbfb3
--- /dev/null
+++ b/android/app/src/main/java/app/organicmaps/editor/OsmLoginBottomFragment.java
@@ -0,0 +1,124 @@
+package app.organicmaps.editor;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ProgressBar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
+import com.google.android.material.textfield.TextInputEditText;
+
+import app.organicmaps.R;
+import app.organicmaps.util.Constants;
+import app.organicmaps.util.InputUtils;
+import app.organicmaps.util.UiUtils;
+import app.organicmaps.util.Utils;
+import app.organicmaps.util.concurrency.ThreadPool;
+import app.organicmaps.util.concurrency.UiThread;
+
+public class OsmLoginBottomFragment extends BottomSheetDialogFragment
+{
+ final private OsmLoginFragment parentFragment;
+ private TextInputEditText mLoginInput;
+ private TextInputEditText mPasswordInput;
+ private Button mLoginButton;
+ private Button mLostPasswordButton;
+ private Button mRegisterButton;
+ private ProgressBar mProgress;
+
+ public OsmLoginBottomFragment(OsmLoginFragment parentFragment)
+ {
+ super();
+ this.parentFragment = parentFragment;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState)
+ {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
+ {
+ return inflater.inflate(R.layout.fragment_osm_login_bottom, container, false);
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState)
+ {
+ BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);
+ dialog.getBehavior().setState(BottomSheetBehavior.STATE_EXPANDED);
+ return dialog;
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
+ {
+ mLoginInput = view.findViewById(R.id.osm_username);
+ mPasswordInput = view.findViewById(R.id.osm_password);
+
+ mLoginButton = view.findViewById(R.id.login);
+ mLoginButton.setOnClickListener((v) -> doLogin());
+
+ mLostPasswordButton = view.findViewById(R.id.lost_password);
+ mLostPasswordButton.setOnClickListener((v) -> Utils.openUrl(requireActivity(), Constants.Url.OSM_RECOVER_PASSWORD));
+ mRegisterButton = view.findViewById(R.id.register);
+ mRegisterButton.setOnClickListener((v) -> Utils.openUrl(requireActivity(), Constants.Url.OSM_REGISTER));
+ mProgress = view.findViewById(R.id.osm_login_progress);
+ }
+
+ private void doLogin()
+ {
+ InputUtils.hideKeyboard(mLoginInput);
+ final String username = mLoginInput.getText().toString().trim();
+ final String password = mPasswordInput.getText().toString();
+ enableInput(false);
+ UiUtils.show(mProgress);
+ mLoginButton.setText("");
+
+ ThreadPool.getWorker().execute(() ->
+ {
+ final String oauthToken = OsmOAuth.nativeAuthWithPassword(username, password);
+ final String username1 = (oauthToken == null) ? null : OsmOAuth.nativeGetOsmUsername(oauthToken);
+ UiThread.run(() -> processAuth(oauthToken, username1));
+ });
+ }
+
+ private void processAuth(String oauthToken, String username)
+ {
+ if (!isAdded())
+ return;
+
+ enableInput(true);
+ UiUtils.hide(mProgress);
+ mLoginButton.setText(R.string.login_osm);
+
+ if (oauthToken == null)
+ parentFragment.onAuthFail();
+ else
+ {
+ this.dismiss();
+ parentFragment.onAuthSuccess(oauthToken, username);
+ }
+ }
+
+ private void enableInput(boolean enable)
+ {
+ mPasswordInput.setEnabled(enable);
+ mLoginInput.setEnabled(enable);
+ mLoginButton.setEnabled(enable);
+ mLostPasswordButton.setEnabled(enable);
+ mRegisterButton.setEnabled(enable);
+ }
+}
diff --git a/android/app/src/main/java/app/organicmaps/editor/OsmLoginFragment.java b/android/app/src/main/java/app/organicmaps/editor/OsmLoginFragment.java
index 4e33db9e22..a82cba792a 100644
--- a/android/app/src/main/java/app/organicmaps/editor/OsmLoginFragment.java
+++ b/android/app/src/main/java/app/organicmaps/editor/OsmLoginFragment.java
@@ -1,37 +1,34 @@
package app.organicmaps.editor;
+import android.content.ActivityNotFoundException;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
-import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
import app.organicmaps.Framework;
import app.organicmaps.R;
import app.organicmaps.base.BaseMwmToolbarFragment;
-import app.organicmaps.util.Constants;
import app.organicmaps.util.DateUtils;
-import app.organicmaps.util.InputUtils;
-import app.organicmaps.util.UiUtils;
import app.organicmaps.util.Utils;
import app.organicmaps.util.concurrency.ThreadPool;
import app.organicmaps.util.concurrency.UiThread;
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.textfield.TextInputEditText;
public class OsmLoginFragment extends BaseMwmToolbarFragment
{
- private ProgressBar mProgress;
- private Button mLoginButton;
- private Button mLostPasswordButton;
- private TextInputEditText mLoginInput;
- private TextInputEditText mPasswordInput;
+ private Button mLoginUsernameButton;
+ private Button mLoginWebsiteButton;
+
+ private String mArgOAuth2Code;
@Nullable
@Override
@@ -45,71 +42,85 @@ public class OsmLoginFragment extends BaseMwmToolbarFragment
{
super.onViewCreated(view, savedInstanceState);
getToolbarController().setTitle(R.string.login);
- mLoginInput = view.findViewById(R.id.osm_username);
- mPasswordInput = view.findViewById(R.id.osm_password);
- mLoginButton = view.findViewById(R.id.login);
- mLoginButton.setOnClickListener((v) -> login());
- mLostPasswordButton = view.findViewById(R.id.lost_password);
- mLostPasswordButton.setOnClickListener((v) -> Utils.openUrl(requireActivity(), Constants.Url.OSM_RECOVER_PASSWORD));
- Button registerButton = view.findViewById(R.id.register);
- registerButton.setOnClickListener((v) -> Utils.openUrl(requireActivity(), Constants.Url.OSM_REGISTER));
- mProgress = view.findViewById(R.id.osm_login_progress);
+
+ mLoginWebsiteButton = view.findViewById(R.id.login_website);
+ mLoginWebsiteButton.setOnClickListener((v) -> loginWithBrowser());
+ mLoginUsernameButton = view.findViewById(R.id.login_username);
+ mLoginUsernameButton.setOnClickListener((v) -> loginUsername());
final String dataVersion = DateUtils.getShortDateFormatter().format(Framework.getDataVersion());
((TextView) view.findViewById(R.id.osm_presentation))
.setText(getString(R.string.osm_presentation, dataVersion));
+
+ readArguments();
+ if (mArgOAuth2Code != null && !mArgOAuth2Code.isEmpty())
+ continueOAuth2Flow(mArgOAuth2Code);
}
- private void login()
+ private void readArguments()
{
- InputUtils.hideKeyboard(mLoginInput);
- final String username = mLoginInput.getText().toString().trim();
- final String password = mPasswordInput.getText().toString();
- enableInput(false);
- UiUtils.show(mProgress);
- mLoginButton.setText("");
+ final Bundle arguments = getArguments();
+ if (arguments == null)
+ return;
- ThreadPool.getWorker().execute(() -> {
- final String oauthToken = OsmOAuth.nativeAuthWithPassword(username, password);
- final String username1 = (oauthToken == null) ? null : OsmOAuth.nativeGetOsmUsername(oauthToken);
- UiThread.run(() -> processAuth(oauthToken, username1));
- });
+ mArgOAuth2Code = arguments.getString(OsmLoginActivity.EXTRA_OAUTH2CODE);
}
- private void enableInput(boolean enable)
+ private void loginUsername()
{
- mPasswordInput.setEnabled(enable);
- mLoginInput.setEnabled(enable);
- mLoginButton.setEnabled(enable);
- mLostPasswordButton.setEnabled(enable);
+ OsmLoginBottomFragment bottomSheetFragment = new OsmLoginBottomFragment(this);
+ bottomSheetFragment.show(requireActivity().getSupportFragmentManager(), bottomSheetFragment.getTag());
}
- private void processAuth(String oauthToken, String username)
+ private void loginWithBrowser()
+ {
+ Utils.openUri(requireContext(), Uri.parse(OsmOAuth.nativeGetOAuth2Url()));
+ }
+
+ // This method is called by MwmActivity & UrlProcessor when "om://oauth2/osm/callback?code=XXX" is handled
+ private void continueOAuth2Flow(String oauth2code)
+ {
+ if (!isAdded())
+ return;
+
+ if (oauth2code == null || oauth2code.isEmpty())
+ onAuthFail();
+ else
+ {
+ ThreadPool.getWorker().execute(() -> {
+ // Finish OAuth2 auth flow and get username for UI.
+ final String oauthToken = OsmOAuth.nativeAuthWithOAuth2Code(oauth2code);
+ final String username = (oauthToken == null) ? null : OsmOAuth.nativeGetOsmUsername(oauthToken);
+ UiThread.run(() -> {
+ processAuth(oauthToken, username);
+ });
+ });
+ }
+ }
+
+ public void processAuth(String oauthToken, String username)
{
if (!isAdded())
return;
- enableInput(true);
- UiUtils.hide(mProgress);
- mLoginButton.setText(R.string.login_osm);
if (oauthToken == null)
onAuthFail();
else
onAuthSuccess(oauthToken, username);
}
- private void onAuthFail()
+ public void onAuthFail()
{
new MaterialAlertDialogBuilder(requireActivity(), R.style.MwmTheme_AlertDialog)
- .setTitle(R.string.editor_login_error_dialog)
- .setPositiveButton(R.string.ok, null)
- .show();
+ .setTitle(R.string.editor_login_error_dialog)
+ .setPositiveButton(R.string.ok, null)
+ .show();
}
- private void onAuthSuccess(String oauthToken, String username)
+ public void onAuthSuccess(String oauthToken, String username)
{
OsmOAuth.setAuthorization(requireContext(), oauthToken, username);
final Bundle extras = requireActivity().getIntent().getExtras();
- if (extras != null && extras.getBoolean("redirectToProfile", false))
+ if (extras != null && extras.getBoolean(ProfileActivity.EXTRA_REDIRECT_TO_PROFILE, false))
startActivity(new Intent(requireContext(), ProfileActivity.class));
requireActivity().finish();
}
diff --git a/android/app/src/main/java/app/organicmaps/editor/OsmOAuth.java b/android/app/src/main/java/app/organicmaps/editor/OsmOAuth.java
index d5d7dc8450..a8afa2f3b6 100644
--- a/android/app/src/main/java/app/organicmaps/editor/OsmOAuth.java
+++ b/android/app/src/main/java/app/organicmaps/editor/OsmOAuth.java
@@ -10,6 +10,8 @@ import androidx.annotation.Size;
import androidx.annotation.WorkerThread;
import androidx.fragment.app.FragmentManager;
+import java.util.Map;
+
import app.organicmaps.MwmApplication;
import app.organicmaps.util.NetworkPolicy;
@@ -98,6 +100,12 @@ public final class OsmOAuth
return nativeGetHistoryUrl(getUsername(context));
}
+ /*
+ Returns 5 strings: ServerURL, ClientId, ClientSecret, Scope, RedirectUri
+ */
+ @NonNull
+ public static native String nativeGetOAuth2Url();
+
/**
* @return string with OAuth2 token
*/
@@ -106,6 +114,13 @@ public final class OsmOAuth
@Nullable
public static native String nativeAuthWithPassword(String login, String password);
+ /**
+ * @return string with OAuth2 token
+ */
+ @WorkerThread
+ @Nullable
+ public static native String nativeAuthWithOAuth2Code(String oauth2code);
+
@WorkerThread
@Nullable
public static native String nativeGetOsmUsername(String oauthToken);
diff --git a/android/app/src/main/java/app/organicmaps/editor/ProfileActivity.java b/android/app/src/main/java/app/organicmaps/editor/ProfileActivity.java
index eb5a6076c3..625b54ef3a 100644
--- a/android/app/src/main/java/app/organicmaps/editor/ProfileActivity.java
+++ b/android/app/src/main/java/app/organicmaps/editor/ProfileActivity.java
@@ -6,6 +6,8 @@ import app.organicmaps.base.BaseMwmFragmentActivity;
public class ProfileActivity extends BaseMwmFragmentActivity
{
+ public static final String EXTRA_REDIRECT_TO_PROFILE = "redirectToProfile";
+
@Override
protected Class extends Fragment> getFragmentClass()
{
diff --git a/android/app/src/main/java/app/organicmaps/editor/ProfileFragment.java b/android/app/src/main/java/app/organicmaps/editor/ProfileFragment.java
index 95ff55c045..4f48fc11b0 100644
--- a/android/app/src/main/java/app/organicmaps/editor/ProfileFragment.java
+++ b/android/app/src/main/java/app/organicmaps/editor/ProfileFragment.java
@@ -93,7 +93,7 @@ public class ProfileFragment extends BaseMwmToolbarFragment
else
{
Intent intent = new Intent(requireContext(), OsmLoginActivity.class);
- intent.putExtra("redirectToProfile", true);
+ intent.putExtra(ProfileActivity.EXTRA_REDIRECT_TO_PROFILE, true);
startActivity(intent);
requireActivity().finish();
}
diff --git a/android/app/src/main/java/app/organicmaps/intent/Factory.java b/android/app/src/main/java/app/organicmaps/intent/Factory.java
index 2ddf4a28c6..c58a84e09e 100644
--- a/android/app/src/main/java/app/organicmaps/intent/Factory.java
+++ b/android/app/src/main/java/app/organicmaps/intent/Factory.java
@@ -3,6 +3,7 @@ package app.organicmaps.intent;
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.content.IntentCompat;
@@ -18,6 +19,7 @@ import app.organicmaps.api.RoutePoint;
import app.organicmaps.bookmarks.data.BookmarkManager;
import app.organicmaps.bookmarks.data.FeatureId;
import app.organicmaps.bookmarks.data.MapObject;
+import app.organicmaps.editor.OsmLoginActivity;
import app.organicmaps.routing.RoutingController;
import app.organicmaps.search.SearchActivity;
import app.organicmaps.search.SearchEngine;
@@ -128,6 +130,15 @@ public class Factory
Framework.nativeSetViewportCenter(latlon[0], latlon[1], SEARCH_IN_VIEWPORT_ZOOM);
}
+ return true;
+ }
+ case RequestType.OAUTH2:
+ {
+ SearchEngine.INSTANCE.cancelInteractiveSearch();
+
+ final String oauth2code = Framework.nativeGetParsedOAuth2Code();
+ OsmLoginActivity.OAuth2Callback(target, oauth2code);
+
return true;
}
}
diff --git a/android/app/src/main/res/layout-land/fragment_osm_login.xml b/android/app/src/main/res/layout-land/fragment_osm_login.xml
index e5b177f708..a811956ee2 100644
--- a/android/app/src/main/res/layout-land/fragment_osm_login.xml
+++ b/android/app/src/main/res/layout-land/fragment_osm_login.xml
@@ -63,119 +63,32 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_marginBottom="@dimen/margin_base">
+
diff --git a/android/app/src/main/res/layout-land/fragment_osm_login_bottom.xml b/android/app/src/main/res/layout-land/fragment_osm_login_bottom.xml
new file mode 100644
index 0000000000..8f360f5960
--- /dev/null
+++ b/android/app/src/main/res/layout-land/fragment_osm_login_bottom.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/fragment_osm_login.xml b/android/app/src/main/res/layout/fragment_osm_login.xml
index 3868214a12..1951fdb17b 100644
--- a/android/app/src/main/res/layout/fragment_osm_login.xml
+++ b/android/app/src/main/res/layout/fragment_osm_login.xml
@@ -60,83 +60,22 @@
android:text="@string/login_to_make_edits_visible"
android:textAppearance="@style/MwmTextAppearance.Body2"
android:textColor="?android:textColorPrimary" />
-
-
-
-
-
-
-
-
-
-
+ android:text="Login with OSM website"
+ android:textAppearance="@style/MwmTextAppearance.Body2.Light" />
-
-
-
+ android:textAppearance="@style/MwmTextAppearance.Body2.Light" />
diff --git a/android/app/src/main/res/layout/fragment_osm_login_bottom.xml b/android/app/src/main/res/layout/fragment_osm_login_bottom.xml
new file mode 100644
index 0000000000..debdbaf6f8
--- /dev/null
+++ b/android/app/src/main/res/layout/fragment_osm_login_bottom.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/editor/osm_auth.cpp b/editor/osm_auth.cpp
index 869dc25a77..48c4808a00 100644
--- a/editor/osm_auth.cpp
+++ b/editor/osm_auth.cpp
@@ -241,14 +241,7 @@ string OsmOAuth::SendAuthRequest(string const & requestTokenKey, SessionID const
string OsmOAuth::FetchRequestToken(SessionID const & sid) const
{
- string const requestTokenUrl = m_baseUrl + "/oauth2/authorize";
- string const requestTokenQuery = BuildPostRequest({
- {"client_id", m_oauth2params.m_clientId},
- {"redirect_uri", m_oauth2params.m_redirectUri},
- {"scope", m_oauth2params.m_scope},
- {"response_type", "code"}
- });
- HttpClient request(requestTokenUrl + "?" + requestTokenQuery);
+ HttpClient request(BuildOAuth2Url());
request.SetCookies(sid.m_cookies)
.SetFollowRedirects(false);
@@ -280,6 +273,18 @@ string OsmOAuth::FetchRequestToken(SessionID const & sid) const
}
}
+string OsmOAuth::BuildOAuth2Url() const
+{
+ string requestTokenUrl = m_baseUrl + "/oauth2/authorize";
+ string const requestTokenQuery = BuildPostRequest({
+ {"client_id", m_oauth2params.m_clientId},
+ {"redirect_uri", m_oauth2params.m_redirectUri},
+ {"scope", m_oauth2params.m_scope},
+ {"response_type", "code"}
+ });
+ return requestTokenUrl.append("?").append(requestTokenQuery);
+}
+
string OsmOAuth::FinishAuthorization(string const & oauth2code) const
{
auto params = BuildPostRequest({
diff --git a/editor/osm_auth.hpp b/editor/osm_auth.hpp
index 2c80be851e..27320ff7a3 100644
--- a/editor/osm_auth.hpp
+++ b/editor/osm_auth.hpp
@@ -93,6 +93,13 @@ public:
/// @param[api] If false, request is made to m_baseUrl.
Response DirectRequest(std::string const & method, bool api = true) const;
+ // Getters
+ std::string GetBaseUrl() const { return m_baseUrl; }
+ std::string GetClientId() const { return m_oauth2params.m_clientId; }
+ std::string GetClientSecret() const { return m_oauth2params.m_clientSecret; }
+ std::string GetScope() const { return m_oauth2params.m_scope; }
+ std::string GetRedirectUri() const { return m_oauth2params.m_redirectUri; }
+
/// @name Methods for WebView-based authentication.
//@{
std::string FinishAuthorization(std::string const & oauth2code) const;
@@ -102,6 +109,7 @@ public:
{
return m_baseUrl + "/user/" + user + "/history";
}
+ std::string BuildOAuth2Url() const;
//@}
private:
@@ -136,7 +144,6 @@ private:
/// @returns valid key and secret or throws otherwise.
std::string FetchRequestToken(SessionID const & sid) const;
std::string FetchAccessToken(SessionID const & sid) const;
- //AuthResult FetchAccessToken(SessionID const & sid);
};
std::string DebugPrint(OsmOAuth::Response const & code);
diff --git a/iphone/CoreApi/CoreApi/DeepLink/DeepLinkParser.h b/iphone/CoreApi/CoreApi/DeepLink/DeepLinkParser.h
index f13b642151..d62b92104d 100644
--- a/iphone/CoreApi/CoreApi/DeepLink/DeepLinkParser.h
+++ b/iphone/CoreApi/CoreApi/DeepLink/DeepLinkParser.h
@@ -8,6 +8,7 @@ typedef NS_ENUM(NSUInteger, DeeplinkUrlType) {
DeeplinkUrlTypeRoute,
DeeplinkUrlTypeSearch,
DeeplinkUrlTypeCrosshair,
+ DeeplinkUrlTypeOAuth2
};
@interface DeepLinkParser : NSObject
diff --git a/iphone/CoreApi/CoreApi/DeepLink/DeepLinkParser.mm b/iphone/CoreApi/CoreApi/DeepLink/DeepLinkParser.mm
index 59d8800600..f50f0a2b0a 100644
--- a/iphone/CoreApi/CoreApi/DeepLink/DeepLinkParser.mm
+++ b/iphone/CoreApi/CoreApi/DeepLink/DeepLinkParser.mm
@@ -13,6 +13,7 @@ static inline DeeplinkUrlType deeplinkUrlType(url_scheme::ParsedMapApi::UrlType
case url_scheme::ParsedMapApi::UrlType::Route: return DeeplinkUrlTypeRoute;
case url_scheme::ParsedMapApi::UrlType::Search: return DeeplinkUrlTypeSearch;
case url_scheme::ParsedMapApi::UrlType::Crosshair: return DeeplinkUrlTypeCrosshair;
+ case url_scheme::ParsedMapApi::UrlType::OAuth2: return DeeplinkUrlTypeOAuth2;
}
}
diff --git a/iphone/Maps/Core/DeepLink/DeepLinkHandler.swift b/iphone/Maps/Core/DeepLink/DeepLinkHandler.swift
index 6629a4d4d8..04e6e23a2c 100644
--- a/iphone/Maps/Core/DeepLink/DeepLinkHandler.swift
+++ b/iphone/Maps/Core/DeepLink/DeepLinkHandler.swift
@@ -115,6 +115,10 @@
case .crosshair:
// Not supported on iOS.
return false;
+
+ case .oAuth2:
+ // TODO: support OAuth2
+ return false;
case .incorrect:
// Invalid URL or API parameters.
diff --git a/map/framework.cpp b/map/framework.cpp
index e81b282ceb..eff1b1b136 100644
--- a/map/framework.cpp
+++ b/map/framework.cpp
@@ -1738,6 +1738,11 @@ url_scheme::SearchRequest Framework::GetParsedSearchRequest() const
return m_parsedMapApi.GetSearchRequest();
}
+std::string Framework::GetParsedOAuth2Code() const
+{
+ return m_parsedMapApi.GetOAuth2Code();
+}
+
std::string const & Framework::GetParsedAppName() const
{
return m_parsedMapApi.GetAppName();
diff --git a/map/framework.hpp b/map/framework.hpp
index e345b7d721..6b7ea84dbe 100644
--- a/map/framework.hpp
+++ b/map/framework.hpp
@@ -578,6 +578,7 @@ public:
ParsedRoutingData GetParsedRoutingData() const;
url_scheme::SearchRequest GetParsedSearchRequest() const;
+ std::string GetParsedOAuth2Code() const;
std::string const & GetParsedAppName() const;
std::string const & GetParsedBackUrl() const;
ms::LatLon GetParsedCenterLatLon() const;
diff --git a/map/map_tests/mwm_url_tests.cpp b/map/map_tests/mwm_url_tests.cpp
index 6c0e0f1454..401fcf5925 100644
--- a/map/map_tests/mwm_url_tests.cpp
+++ b/map/map_tests/mwm_url_tests.cpp
@@ -474,6 +474,23 @@ UNIT_TEST(AppNameTest)
}
}
+UNIT_TEST(OAuth2Test)
+{
+ {
+ ParsedMapApi api("om://oauth2/osm/callback?code=THE_MEGA_CODE");
+ TEST_EQUAL(api.GetRequestType(), UrlType::OAuth2, ());
+ TEST_EQUAL(api.GetOAuth2Code(), "THE_MEGA_CODE", ());
+ }
+ {
+ ParsedMapApi api("om://oauth2/google/callback?code=THE_MEGA_CODE");
+ TEST_EQUAL(api.GetRequestType(), UrlType::Incorrect, ());
+ }
+ {
+ ParsedMapApi api("om://oauth2/osm/callback?code=");
+ TEST_EQUAL(api.GetRequestType(), UrlType::Incorrect, ());
+ }
+}
+
namespace
{
string generatePartOfUrl(url_scheme::MapPoint const & point)
diff --git a/map/mwm_url.cpp b/map/mwm_url.cpp
index 9eb3dbf8fc..b99e15109d 100644
--- a/map/mwm_url.cpp
+++ b/map/mwm_url.cpp
@@ -175,6 +175,22 @@ ParsedMapApi::UrlType ParsedMapApi::SetUrlAndParse(std::string const & raw)
return m_requestType = UrlType::Crosshair;
}
+ else if (type == "oauth2")
+ {
+ if (url.GetPath() != "osm/callback")
+ return m_requestType = UrlType::Incorrect;
+
+ url.ForEachParam([this](auto const & key, auto const & value)
+ {
+ if (key == "code")
+ m_oauth2code = value;
+ });
+
+ if (m_oauth2code.empty())
+ return m_requestType = UrlType::Incorrect;
+ else
+ return m_requestType = UrlType::OAuth2;
+ }
else if (checkForGe0Link)
{
// The URL is prefixed by one of the kGe0Prefixes AND doesn't match any supported API call:
@@ -386,6 +402,7 @@ void ParsedMapApi::Reset()
m_mapPoints.clear();
m_routePoints.clear();
m_searchRequest = {};
+ m_oauth2code = {};
m_globalBackUrl ={};
m_appName = {};
m_centerLatLon = ms::LatLon::Invalid();
@@ -467,6 +484,7 @@ std::string DebugPrint(ParsedMapApi::UrlType type)
case ParsedMapApi::UrlType::Route: return "Route";
case ParsedMapApi::UrlType::Search: return "Search";
case ParsedMapApi::UrlType::Crosshair: return "Crosshair";
+ case ParsedMapApi::UrlType::OAuth2: return "OAuth2";
}
UNREACHABLE();
}
diff --git a/map/mwm_url.hpp b/map/mwm_url.hpp
index 6862d74794..aef4fe4c20 100644
--- a/map/mwm_url.hpp
+++ b/map/mwm_url.hpp
@@ -45,6 +45,7 @@ public:
Route = 2,
Search = 3,
Crosshair = 4,
+ OAuth2 = 5,
};
ParsedMapApi() = default;
@@ -93,6 +94,12 @@ public:
return m_searchRequest;
}
+ std::string const & GetOAuth2Code() const
+ {
+ ASSERT_EQUAL(m_requestType, UrlType::OAuth2, ("Expected OAuth2 API"));
+ return m_oauth2code;
+ }
+
private:
void ParseMapParam(std::string const & key, std::string const & value,
bool & correctOrder);
@@ -107,6 +114,7 @@ private:
SearchRequest m_searchRequest;
std::string m_globalBackUrl;
std::string m_appName;
+ std::string m_oauth2code;
ms::LatLon m_centerLatLon = ms::LatLon::Invalid();
std::string m_routingType;
int m_version = 0;