diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 75d2bc4de2..559587cd55 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -25,7 +25,7 @@ define add_prebuild_static_lib include $(PREBUILT_STATIC_LIBRARY) endef -prebuild_static_libs := osrm protobuf tomcrypt jansson minizip fribidi freetype expat succinct opening_hours pugixml \ +prebuild_static_libs := osrm protobuf tomcrypt jansson minizip fribidi freetype expat succinct opening_hours pugixml oauthcpp \ base coding geometry editor platform indexer storage search routing drape drape_frontend map stats_client $(foreach item,$(prebuild_static_libs),$(eval $(call add_prebuild_static_lib,$(item)))) @@ -42,7 +42,7 @@ LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../ LOCAL_MODULE := mapswithme LOCAL_STATIC_LIBRARIES := map drape_frontend routing search storage indexer drape platform editor geometry coding base \ - expat freetype fribidi minizip jansson tomcrypt protobuf osrm stats_client succinct opening_hours pugixml + expat freetype fribidi minizip jansson tomcrypt protobuf osrm stats_client succinct opening_hours pugixml oauthcpp LOCAL_CFLAGS := -ffunction-sections -fdata-sections -Wno-extern-c-compat diff --git a/configure.sh b/configure.sh index c990391751..861322f503 100755 --- a/configure.sh +++ b/configure.sh @@ -34,6 +34,8 @@ else #define MY_TARGET_KEY "" #define PARSE_APPLICATION_ID "" #define PARSE_CLIENT_KEY "" +#define OSM_CONSUMER_KEY "" +#define OSM_CONSUMER_SECRET "" #define MWM_GEOLOCATION_SERVER "" #define OSRM_ONLINE_SERVER_URL "" #define RESOURCES_METASERVER_URL "" diff --git a/editor/editor_tests/osm_auth_test.cpp b/editor/editor_tests/osm_auth_test.cpp index 5d7df5e7fa..5ef229f9b4 100644 --- a/editor/editor_tests/osm_auth_test.cpp +++ b/editor/editor_tests/osm_auth_test.cpp @@ -32,17 +32,18 @@ UNIT_TEST(OSM_Auth_Login) auto result = auth.AuthorizePassword(kTestUser, kTestPassword, token); TEST_EQUAL(result, OsmOAuth::AuthResult::OK, ("login to test server")); TEST(token.IsValid(), ("authorized")); - string const perm = auth.Request(token, "/permissions"); - TEST(perm.find("write_api") != string::npos, ("can write to api")); + OsmOAuth::Response const perm = auth.Request(token, "/permissions"); + TEST_EQUAL(perm.first, OsmOAuth::ResponseCode::OK, ("permission request ok")); + TEST(perm.second.find("write_api") != string::npos, ("can write to api")); } UNIT_TEST(OSM_Auth_Facebook) { OsmOAuth auth(kConsumerKey, kConsumerSecret, kTestServer, kTestServer); - ClientToken token; - auto result = auth.AuthorizeFacebook(kFacebookToken, token); + auto result = auth.AuthorizeFacebook(kFacebookToken); TEST_EQUAL(result, OsmOAuth::AuthResult::OK, ("login via facebook")); - TEST(token.IsValid(), ("authorized")); - string const perm = auth.Request(token, "/permissions"); - TEST(perm.find("write_api") != string::npos, ("can write to api")); + TEST(auth.IsAuthorized(), ("authorized")); + OsmOAuth::Response const perm = auth.Request("/permissions"); + TEST_EQUAL(perm.first, OsmOAuth::ResponseCode::OK, ("permission with stored token request ok")); + TEST(perm.second.find("write_api") != string::npos, ("can write to api")); } diff --git a/editor/editor_tests/server_api_test.cpp b/editor/editor_tests/server_api_test.cpp index dbec0ad0fa..9d29cfb239 100644 --- a/editor/editor_tests/server_api_test.cpp +++ b/editor/editor_tests/server_api_test.cpp @@ -7,29 +7,36 @@ #include "3party/pugixml/src/pugixml.hpp" using osm::ServerApi06; +using osm::OsmOAuth; using namespace pugi; constexpr char const * kOsmDevServer = "http://master.apis.dev.openstreetmap.org"; +constexpr char const * kOsmConsumerKey = "eRtN6yKZZf34oVyBnyaVbsWtHIIeptLArQKdTwN3"; +constexpr char const * kOsmConsumerSecret = "lC124mtm2VqvKJjSh35qBpKfrkeIjpKuGe38Hd1H"; constexpr char const * kValidOsmUser = "MapsMeTestUser"; constexpr char const * kInvalidOsmUser = "qwesdxzcgretwr"; -ServerApi06 const kApi(kValidOsmUser, "12345678", kOsmDevServer); +constexpr char const * kValidOsmPassword = "12345678"; -UNIT_TEST(OSM_ServerAPI_CheckUserAndPassword) +UNIT_TEST(OSM_ServerAPI_TestUserExists) { - TEST(kApi.CheckUserAndPassword(), ()); - - my::LogLevelSuppressor s; - TEST(!ServerApi06(kInvalidOsmUser, "3345dfce2", kOsmDevServer).CheckUserAndPassword(), ()); -} - -UNIT_TEST(OSM_ServerAPI_HttpCodeForUrl) -{ - TEST_EQUAL(200, ServerApi06::HttpCodeForUrl(string(kOsmDevServer) + "/user/" + kValidOsmUser), ()); - TEST_EQUAL(404, ServerApi06::HttpCodeForUrl(string(kOsmDevServer) + "/user/" + kInvalidOsmUser), ()); + OsmOAuth auth(kOsmConsumerKey, kOsmConsumerSecret, kOsmDevServer); + ServerApi06 api(auth); + TEST(api.TestUserExists(kValidOsmUser), ()); + TEST(!api.TestUserExists(kInvalidOsmUser), ()); } namespace { +ServerApi06 CreateAPI() +{ + OsmOAuth auth(kOsmConsumerKey, kOsmConsumerSecret, kOsmDevServer); + OsmOAuth::AuthResult result = auth.AuthorizePassword(kValidOsmUser, kValidOsmPassword); + TEST_EQUAL(result, OsmOAuth::AuthResult::OK, ()); + TEST(auth.IsAuthorized(), ("OSM authorization")); + ServerApi06 api(auth); + return api; +} + // id attribute is set to -1. // version attribute is set to 1. void GenerateNodeXml(double lat, double lon, ServerApi06::TKeyValueTags const & tags, xml_document & outNode) @@ -88,36 +95,38 @@ UNIT_TEST(SetAttributeForOsmNode) UNIT_TEST(OSM_ServerAPI_ChangesetActions) { + ServerApi06 api = CreateAPI(); + uint64_t changeSetId; - TEST(kApi.CreateChangeSet({{"created_by", "MAPS.ME Unit Test"}, {"comment", "For test purposes only."}}, changeSetId), ()); + TEST(api.CreateChangeSet({{"created_by", "MAPS.ME Unit Test"}, {"comment", "For test purposes only."}}, changeSetId), ()); xml_document node; GenerateNodeXml(11.11, 12.12, {{"testkey", "firstnode"}}, node); TEST(SetAttributeForOsmNode(node, "changeset", changeSetId), ()); uint64_t nodeId; - TEST(kApi.CreateNode(XmlToString(node), nodeId), ()); + TEST(api.CreateNode(XmlToString(node), nodeId), ()); TEST(SetAttributeForOsmNode(node, "id", nodeId), ()); TEST(SetAttributeForOsmNode(node, "lat", 10.10), ()); - TEST(kApi.ModifyNode(XmlToString(node), nodeId), ()); + TEST(api.ModifyNode(XmlToString(node), nodeId), ()); // After modification, node version has increased. TEST(SetAttributeForOsmNode(node, "version", 2), ()); // To retrieve created node, changeset should be closed first. - TEST(kApi.CloseChangeSet(changeSetId), ()); + TEST(api.CloseChangeSet(changeSetId), ()); - TEST(kApi.CreateChangeSet({{"created_by", "MAPS.ME Unit Test"}, {"comment", "For test purposes only."}}, changeSetId), ()); + TEST(api.CreateChangeSet({{"created_by", "MAPS.ME Unit Test"}, {"comment", "For test purposes only."}}, changeSetId), ()); // New changeset has new id. TEST(SetAttributeForOsmNode(node, "changeset", changeSetId), ()); - string const serverReply = kApi.GetXmlNodeByLatLon(node.child("osm").child("node").attribute("lat").as_double(), + string const serverReply = api.GetXmlNodeByLatLon(node.child("osm").child("node").attribute("lat").as_double(), node.child("osm").child("node").attribute("lon").as_double()); xml_document reply; reply.load_string(serverReply.c_str()); TEST_EQUAL(nodeId, reply.child("osm").child("node").attribute("id").as_ullong(), ()); - TEST(ServerApi06::DeleteResult::ESuccessfullyDeleted == kApi.DeleteNode(XmlToString(node), nodeId), ()); + TEST(ServerApi06::DeleteResult::ESuccessfullyDeleted == api.DeleteNode(XmlToString(node), nodeId), ()); - TEST(kApi.CloseChangeSet(changeSetId), ()); + TEST(api.CloseChangeSet(changeSetId), ()); } diff --git a/editor/osm_auth.cpp b/editor/osm_auth.cpp index 60a4970410..c03cebd3d8 100644 --- a/editor/osm_auth.cpp +++ b/editor/osm_auth.cpp @@ -49,6 +49,17 @@ bool isLoggedIn(string const & contents) } } // namespace +OsmOAuth::OsmOAuth(string const & consumerKey, string const & consumerSecret, + string const & baseUrl, string const & apiUrl): + m_consumerKey(consumerKey), m_consumerSecret(consumerSecret), m_baseUrl(baseUrl) +{ + // If base URL has been changed, but api URL is omitted, we use the same base URL for api calls. + if (apiUrl.empty() || (apiUrl == kDefaultApiURL && baseUrl != kDefaultBaseURL)) + m_apiUrl = baseUrl; + else + m_apiUrl = apiUrl; +} + // Opens a login page and extract a cookie and a secret token. OsmOAuth::AuthResult OsmOAuth::FetchSessionId(OsmOAuth::SessionID & sid) const { @@ -120,6 +131,8 @@ string OsmOAuth::SendAuthRequest(string const & requestTokenKey, SessionID const params["authenticity_token"] = sid.m_token; params["allow_read_prefs"] = "yes"; params["allow_write_api"] = "yes"; + params["allow_write_gpx"] = "yes"; + params["allow_write_notes"] = "yes"; params["commit"] = "Save changes"; HTTPClientPlatformWrapper request(m_baseUrl + "/oauth/authorize"); request.set_body_data(buildPostRequest(params), "application/x-www-form-urlencoded"); @@ -202,8 +215,9 @@ OsmOAuth::AuthResult OsmOAuth::AuthorizeFacebook(string const & facebookToken, C return FetchAccessToken(sid, token); } -string OsmOAuth::Request(ClientToken const & token, string const & method, string const & httpMethod, string const & body) const +OsmOAuth::Response OsmOAuth::Request(ClientToken const & token, string const & method, string const & httpMethod, string const & body) const { + CHECK(token.IsValid(), ("Empty request token")); OAuth::Consumer const consumer(m_consumerKey, m_consumerSecret); OAuth::Token const oatoken(token.m_key, token.m_secret); OAuth::Client oauth(&consumer, &oatoken); @@ -215,22 +229,93 @@ string OsmOAuth::Request(ClientToken const & token, string const & method, strin reqType = OAuth::Http::Post; else if (httpMethod == "PUT") reqType = OAuth::Http::Put; + else if (httpMethod == "DELETE") + reqType = OAuth::Http::Delete; else { ASSERT(false, ("Unsupported OSM API request method", httpMethod)); - return string(); + return Response(ResponseCode::ServerError, string()); } string const url = m_apiUrl + kApiVersion + method; - string const query = oauth.getURLQueryString(reqType, url, body); + string const query = oauth.getURLQueryString(reqType, url); HTTPClientPlatformWrapper request(url + "?" + query); if (httpMethod != "GET") request.set_body_data(body, "application/xml", httpMethod); - if (request.RunHTTPRequest() && request.error_code() == 200 && !request.was_redirected()) - return request.server_response(); + if (!request.RunHTTPRequest() || request.was_redirected()) + return Response(ResponseCode::ServerError, string()); + return Response(static_cast(request.error_code()), request.server_response()); +} - return string(); +OsmOAuth::Response OsmOAuth::DirectRequest(string const & method, bool api) const +{ + string const url = api ? m_apiUrl + kApiVersion + method : m_baseUrl + method; + HTTPClientPlatformWrapper request(url); + if (!request.RunHTTPRequest()) + return Response(ResponseCode::ServerError, string()); + return Response(static_cast(request.error_code()), request.server_response()); +} + +void OsmOAuth::SetToken(ClientToken const & token) +{ + m_token.m_key = token.m_key; + m_token.m_secret = token.m_secret; +} + +OsmOAuth::AuthResult OsmOAuth::FetchAccessToken(SessionID const & sid) +{ + return FetchAccessToken(sid, m_token); +} + +OsmOAuth::AuthResult OsmOAuth::AuthorizePassword(string const & login, string const & password) +{ + return AuthorizePassword(login, password, m_token); +} + +OsmOAuth::AuthResult OsmOAuth::AuthorizeFacebook(string const & facebookToken) +{ + return AuthorizeFacebook(facebookToken, m_token); +} + +OsmOAuth::Response OsmOAuth::Request(string const & method, string const & httpMethod, string const & body) const +{ + return Request(m_token, method, httpMethod, body); +} + +string DebugPrint(OsmOAuth::AuthResult const res) +{ + switch (res) + { + case OsmOAuth::AuthResult::OK: return "OK"; + case OsmOAuth::AuthResult::FailCookie: return "FailCookie"; + case OsmOAuth::AuthResult::FailLogin: return "FailLogin"; + case OsmOAuth::AuthResult::NoOAuth: return "NoOAuth"; + case OsmOAuth::AuthResult::FailAuth: return "FailAuth"; + case OsmOAuth::AuthResult::NoAccess: return "NoAccess"; + case OsmOAuth::AuthResult::NetworkError: return "NetworkError"; + case OsmOAuth::AuthResult::ServerError: return "ServerError"; + } + return "Unknown"; +} + +string DebugPrint(OsmOAuth::ResponseCode const code) +{ + switch (code) + { + case OsmOAuth::ResponseCode::ServerError: return "ServerError"; + case OsmOAuth::ResponseCode::OK: return "OK"; + case OsmOAuth::ResponseCode::BadXML: return "BadXML"; + case OsmOAuth::ResponseCode::Redacted: return "Redacted"; + case OsmOAuth::ResponseCode::NotFound: return "NotFound"; + case OsmOAuth::ResponseCode::WrongMethod: return "WrongMethod"; + case OsmOAuth::ResponseCode::Conflict: return "Conflict"; + case OsmOAuth::ResponseCode::Gone: return "Gone"; + case OsmOAuth::ResponseCode::RefError: return "RefError"; + case OsmOAuth::ResponseCode::URITooLong: return "URITooLong"; + case OsmOAuth::ResponseCode::TooMuchData: return "TooMuchData"; + } + return "Unknown"; } } // namespace osm diff --git a/editor/osm_auth.hpp b/editor/osm_auth.hpp index f8281e296b..30e3d753dc 100644 --- a/editor/osm_auth.hpp +++ b/editor/osm_auth.hpp @@ -5,6 +5,7 @@ namespace osm { +/// A structure that holds request token pair. struct ClientToken { string m_key; @@ -18,7 +19,8 @@ constexpr char const * kDefaultApiURL = "https://api.openstreetmap.org"; class OsmOAuth { public: - enum AuthResult + /// A result of authentication. OK if everything is good. + enum class AuthResult { OK, FailCookie, @@ -30,19 +32,53 @@ public: ServerError }; + /// A result of a request. Has readable values for all OSM API return codes. + enum class ResponseCode + { + ServerError = -1, + OK = 200, + BadXML = 400, + Redacted = 403, + NotFound = 404, + WrongMethod = 405, + Conflict = 409, + Gone = 410, + RefError = 412, + URITooLong = 414, + TooMuchData = 509 + }; + + /// A pair of + using Response = std::pair; + + /// The constructor. Simply stores a lot of strings in fields. + /// @param[apiUrl] The OSM API URL defaults to baseUrl, or kDefaultApiURL if not specified. OsmOAuth(string const & consumerKey, string const & consumerSecret, string const & baseUrl = kDefaultBaseURL, - string const & apiUrl = kDefaultApiURL): - m_consumerKey(consumerKey), - m_consumerSecret(consumerSecret), - m_baseUrl(baseUrl), - m_apiUrl(apiUrl) - { - } + string const & apiUrl = kDefaultApiURL); + /// @name Stateless methods. + //@{ AuthResult AuthorizePassword(string const & login, string const & password, ClientToken & token) const; AuthResult AuthorizeFacebook(string const & facebookToken, ClientToken & token) const; - string Request(ClientToken const & token, string const & method, string const & httpMethod = "GET", string const & body = "") const; + /// @param[method] The API method, must start with a forward slash. + Response Request(ClientToken const & token, string const & method, string const & httpMethod = "GET", string const & body = "") const; + //@} + + /// @name Methods for using a token stored in this class. Obviously not thread-safe. + //@{ + void SetToken(ClientToken const & token); + ClientToken const & GetToken() const { return m_token; } + bool IsAuthorized() const { return m_token.IsValid(); } + AuthResult AuthorizePassword(string const & login, string const & password); + AuthResult AuthorizeFacebook(string const & facebookToken); + /// @param[method] The API method, must start with a forward slash. + Response Request(string const & method, string const & httpMethod = "GET", string const & body = "") const; + //@} + + /// Tokenless GET request, for convenience. + /// @param[api] If false, request is made to m_baseUrl. + Response DirectRequest(string const & method, bool api = true) const; private: struct SessionID @@ -55,6 +91,7 @@ private: string m_consumerSecret; string m_baseUrl; string m_apiUrl; + ClientToken m_token; AuthResult FetchSessionId(SessionID & sid) const; AuthResult LogoutUser(SessionID const & sid) const; @@ -62,6 +99,10 @@ private: AuthResult LoginFacebook(string const & facebookToken, SessionID const & sid) const; string SendAuthRequest(string const & requestTokenKey, SessionID const & sid) const; AuthResult FetchAccessToken(SessionID const & sid, ClientToken & token) const; + AuthResult FetchAccessToken(SessionID const & sid); }; +string DebugPrint(OsmOAuth::AuthResult const res); +string DebugPrint(OsmOAuth::ResponseCode const code); + } // namespace osm diff --git a/editor/server_api.cpp b/editor/server_api.cpp index fc1c62f315..0468d33469 100644 --- a/editor/server_api.cpp +++ b/editor/server_api.cpp @@ -1,5 +1,7 @@ #include "editor/server_api.hpp" +#include "coding/url_encode.hpp" + #include "geometry/mercator.hpp" #include "base/logging.hpp" @@ -7,30 +9,22 @@ #include "std/sstream.hpp" -#include "3party/Alohalytics/src/http_client.h" - -using alohalytics::HTTPClientPlatformWrapper; - namespace osm { -namespace -{ -void PrintRequest(HTTPClientPlatformWrapper const & r) -{ - LOG(LINFO, ("HTTP", r.http_method(), r.url_requested(), "has finished with code", r.error_code(), - (r.was_redirected() ? ", was redirected to " + r.url_received() : ""), - "Server replied:\n", r.server_response())); -} -} // namespace - -ServerApi06::ServerApi06(string const & user, string const & password, string const & baseUrl) - : m_user(user), m_password(password), m_baseOsmServerUrl(baseUrl) +ServerApi06::ServerApi06(OsmOAuth & auth) + : m_auth(auth) { } bool ServerApi06::CreateChangeSet(TKeyValueTags const & kvTags, uint64_t & outChangeSetId) const { + if (!m_auth.IsAuthorized()) + { + LOG(LWARNING, ("Not authorized")); + return false; + } + ostringstream stream; stream << "\n" "\n"; @@ -39,114 +33,72 @@ bool ServerApi06::CreateChangeSet(TKeyValueTags const & kvTags, uint64_t & outCh stream << "\n" "\n"; - HTTPClientPlatformWrapper request(m_baseOsmServerUrl + "/api/0.6/changeset/create"); - bool const success = request.set_user_and_password(m_user, m_password) - .set_body_data(move(stream.str()), "", "PUT") - .RunHTTPRequest(); - if (success && request.error_code() == 200) + OsmOAuth::Response const response = m_auth.Request("/changeset/create", "PUT", move(stream.str())); + if (response.first == OsmOAuth::ResponseCode::OK) { - if (strings::to_uint64(request.server_response(), outChangeSetId)) + if (strings::to_uint64(response.second, outChangeSetId)) return true; LOG(LWARNING, ("Can't parse changeset ID from server response.")); } else - LOG(LWARNING, ("CreateChangeSet request has failed.")); + LOG(LWARNING, ("CreateChangeSet request has failed:", response.first)); - PrintRequest(request); return false; } bool ServerApi06::CreateNode(string const & nodeXml, uint64_t & outCreatedNodeId) const { - HTTPClientPlatformWrapper request(m_baseOsmServerUrl + "/api/0.6/node/create"); - bool const success = request.set_user_and_password(m_user, m_password) - .set_body_data(move(nodeXml), "", "PUT") - .RunHTTPRequest(); - if (success && request.error_code() == 200) + OsmOAuth::Response const response = m_auth.Request("/node/create", "PUT", move(nodeXml)); + if (response.first == OsmOAuth::ResponseCode::OK) { - if (strings::to_uint64(request.server_response(), outCreatedNodeId)) + if (strings::to_uint64(response.second, outCreatedNodeId)) return true; LOG(LWARNING, ("Can't parse created node ID from server response.")); } else - LOG(LWARNING, ("CreateNode request has failed.")); + LOG(LWARNING, ("CreateNode request has failed:", response.first)); - PrintRequest(request); return false; } bool ServerApi06::ModifyNode(string const & nodeXml, uint64_t nodeId) const { - HTTPClientPlatformWrapper request(m_baseOsmServerUrl + "/api/0.6/node/" + strings::to_string(nodeId)); - bool const success = request.set_user_and_password(m_user, m_password) - .set_body_data(move(nodeXml), "", "PUT") - .RunHTTPRequest(); - if (success && request.error_code() == 200) + OsmOAuth::Response const response = m_auth.Request("/node/" + strings::to_string(nodeId), "PUT", move(nodeXml)); + if (response.first == OsmOAuth::ResponseCode::OK) return true; - LOG(LWARNING, ("ModifyNode request has failed.")); - PrintRequest(request); + LOG(LWARNING, ("ModifyNode request has failed:", response.first)); return false; } ServerApi06::DeleteResult ServerApi06::DeleteNode(string const & nodeXml, uint64_t nodeId) const { - HTTPClientPlatformWrapper request(m_baseOsmServerUrl + "/api/0.6/node/" + strings::to_string(nodeId)); - bool const success = request.set_user_and_password(m_user, m_password) - .set_body_data(move(nodeXml), "", "DELETE") - .RunHTTPRequest(); - if (success) - { - switch (request.error_code()) - { - case 200: return DeleteResult::ESuccessfullyDeleted; - case 412: return DeleteResult::ECanNotBeDeleted; - } - } + OsmOAuth::Response const response = m_auth.Request("/node/" + strings::to_string(nodeId), "DELETE", move(nodeXml)); + if (response.first == OsmOAuth::ResponseCode::OK) + return DeleteResult::ESuccessfullyDeleted; + else if (static_cast(response.first) >= 400) + // Tons of reasons, see http://wiki.openstreetmap.org/wiki/API_v0.6#Error_codes_16 + return DeleteResult::ECanNotBeDeleted; - LOG(LWARNING, ("DeleteNode request has failed.")); - PrintRequest(request); + LOG(LWARNING, ("DeleteNode request has failed:", response.first)); return DeleteResult::EFailed; } bool ServerApi06::CloseChangeSet(uint64_t changesetId) const { - HTTPClientPlatformWrapper request(m_baseOsmServerUrl + "/api/0.6/changeset/" + - strings::to_string(changesetId) + "/close"); - bool const success = request.set_user_and_password(m_user, m_password) - .set_http_method("PUT") - .RunHTTPRequest(); - if (success && request.error_code() == 200) + OsmOAuth::Response const response = m_auth.Request("/changeset/" + strings::to_string(changesetId) + "/close", "PUT"); + if (response.first == OsmOAuth::ResponseCode::OK) return true; - LOG(LWARNING, ("CloseChangeSet request has failed.")); - PrintRequest(request); + LOG(LWARNING, ("CloseChangeSet request has failed:", response.first)); return false; } -bool ServerApi06::CheckUserAndPassword() const +bool ServerApi06::TestUserExists(string const & userName) { - static constexpr char const * kAPIWritePermission = "allow_write_api"; - HTTPClientPlatformWrapper request(m_baseOsmServerUrl + "/api/0.6/permissions"); - bool const success = request.set_user_and_password(m_user, m_password).RunHTTPRequest(); - if (success && request.error_code() == 200 && - request.server_response().find(kAPIWritePermission) != string::npos) - return true; - - LOG(LWARNING, ("OSM user and/or password are invalid.")); - PrintRequest(request); - return false; -} - -int ServerApi06::HttpCodeForUrl(string const & url) -{ - HTTPClientPlatformWrapper request(url); - bool const success = request.RunHTTPRequest(); - int const httpCode = request.error_code(); - if (success) - return httpCode; - - return -1; + string const method = "/user/" + UrlEncode(userName); + OsmOAuth::Response const response = m_auth.DirectRequest(method, false); + return response.first == OsmOAuth::ResponseCode::OK; } string ServerApi06::GetXmlFeaturesInRect(m2::RectD const & latLonRect) const @@ -157,15 +109,14 @@ string ServerApi06::GetXmlFeaturesInRect(m2::RectD const & latLonRect) const static constexpr double const kDAC = 7; m2::PointD const lb = latLonRect.LeftBottom(); m2::PointD const rt = latLonRect.RightTop(); - string const url = m_baseOsmServerUrl + "/api/0.6/map?bbox=" + to_string_dac(lb.x, kDAC) + ',' + to_string_dac(lb.y, kDAC) + ',' + + string const url = "/map?bbox=" + to_string_dac(lb.x, kDAC) + ',' + to_string_dac(lb.y, kDAC) + ',' + to_string_dac(rt.x, kDAC) + ',' + to_string_dac(rt.y, kDAC); - HTTPClientPlatformWrapper request(url); - bool const success = request.set_user_and_password(m_user, m_password).RunHTTPRequest(); - if (success && request.error_code() == 200) - return request.server_response(); - LOG(LWARNING, ("GetXmlFeaturesInRect request has failed.")); - PrintRequest(request); + OsmOAuth::Response const response = m_auth.DirectRequest(url); + if (response.first == OsmOAuth::ResponseCode::OK) + return response.second; + + LOG(LWARNING, ("GetXmlFeaturesInRect request has failed:", response.first)); return string(); } diff --git a/editor/server_api.hpp b/editor/server_api.hpp index f4a04842af..713e6aebd6 100644 --- a/editor/server_api.hpp +++ b/editor/server_api.hpp @@ -1,5 +1,7 @@ #pragma once +#include "editor/osm_auth.hpp" + #include "geometry/rect2d.hpp" #include "std/map.hpp" @@ -24,14 +26,9 @@ public: ECanNotBeDeleted }; - ServerApi06(string const & user, string const & password, string const & baseUrl = "http://api.openstreetmap.org"); - /// @returns true if connection with OSM server was established, and user+password are valid. - bool CheckUserAndPassword() const; - /// @returns http server code for given url or negative value in case of error. + ServerApi06(OsmOAuth & auth); /// This function can be used to check if user did not confirm email validation link after registration. - /// For example, for http://www.openstreetmap.org/user/UserName 200 is returned if UserName was registered. - static int HttpCodeForUrl(string const & url); - + bool TestUserExists(string const & userName); /// Please use at least created_by=* and comment=* tags. bool CreateChangeSet(TKeyValueTags const & kvTags, uint64_t & outChangeSetId) const; /// nodeXml should be wrapped into ... tags. @@ -47,9 +44,7 @@ public: string GetXmlNodeByLatLon(double lat, double lon) const; private: - string m_user; - string m_password; - string m_baseOsmServerUrl; + OsmOAuth m_auth; }; } // namespace osm diff --git a/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationCommon.h b/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationCommon.h index 40288c3257..239e765ce4 100644 --- a/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationCommon.h +++ b/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationCommon.h @@ -1,6 +1,6 @@ -static NSString * const kOSMUsernameKey = @"OSMUsernameKey"; -static NSString * const kOSMPasswordKey = @"OSMPasswordKey"; +static NSString * const kOSMRequestToken = @"OSMRequestToken"; +static NSString * const kOSMRequestSecret = @"OSMRequestSecret"; typedef NS_OPTIONS(NSUInteger, MWMAuthorizationButtonType) { diff --git a/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationOSMLoginViewController.mm b/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationOSMLoginViewController.mm index 05cbb3bbf3..6a5a2b7de8 100644 --- a/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationOSMLoginViewController.mm +++ b/iphone/Maps/Classes/CustomViews/Login/MWMAuthorizationOSMLoginViewController.mm @@ -5,6 +5,7 @@ #import "UIColor+MapsMeColor.h" #import "UITextField+RuntimeAttributes.h" +#include "private.h" #include "editor/server_api.hpp" #include "platform/platform.hpp" @@ -98,11 +99,13 @@ typedef NS_OPTIONS(NSUInteger, MWMFieldCorrect) return YES; } -- (void)storeCredentials +- (void)storeCredentials:(osm::ClientToken const *)token { + NSString * requestToken = @(token->m_key.c_str()); + NSString * requestSecret = @(token->m_secret.c_str()); NSUserDefaults * ud = [NSUserDefaults standardUserDefaults]; - [ud setObject:self.loginTextField.text forKey:kOSMUsernameKey]; - [ud setObject:self.passwordTextField.text forKey:kOSMPasswordKey]; + [ud setObject:requestToken forKey:kOSMRequestToken]; + [ud setObject:requestSecret forKey:kOSMRequestSecret]; [self dismissViewControllerAnimated:YES completion:nil]; } @@ -166,12 +169,14 @@ typedef NS_OPTIONS(NSUInteger, MWMFieldCorrect) { string const username = self.loginTextField.text.UTF8String; string const password = self.passwordTextField.text.UTF8String; - BOOL const credentialsOK = osm::ServerApi06(username, password).CheckUserAndPassword(); + osm::OsmOAuth auth(OSM_CONSUMER_KEY, OSM_CONSUMER_SECRET); + osm::ClientToken token; + osm::OsmOAuth::AuthResult const result = auth.AuthorizePassword(username, password, token); dispatch_async(dispatch_get_main_queue(), ^ { [self stopSpinner]; - if (credentialsOK) - [self storeCredentials]; + if (result == osm::OsmOAuth::AuthResult::OK) + [self storeCredentials:&token]; else [self showInvalidCredentialsAlert]; }); @@ -179,7 +184,8 @@ typedef NS_OPTIONS(NSUInteger, MWMFieldCorrect) } else { - [self storeCredentials]; + // Not connected, cannot validate login/password. + // TODO(@igrechuhin) } } diff --git a/iphone/Maps/Classes/Editor/OpeningHours/MWMOpeningHoursEditorViewController.mm b/iphone/Maps/Classes/Editor/OpeningHours/MWMOpeningHoursEditorViewController.mm index fcd62c5681..74dafee2c1 100644 --- a/iphone/Maps/Classes/Editor/OpeningHours/MWMOpeningHoursEditorViewController.mm +++ b/iphone/Maps/Classes/Editor/OpeningHours/MWMOpeningHoursEditorViewController.mm @@ -54,9 +54,9 @@ extern NSDictionary * const kMWMOpeningHoursEditorTableCells = @{ - (void)checkAuthorization { NSUserDefaults * ud = [NSUserDefaults standardUserDefaults]; - NSString * username = [ud stringForKey:kOSMUsernameKey]; - NSString * password = [ud stringForKey:kOSMPasswordKey]; - if (!username || !password) + NSString * requestToken = [ud stringForKey:kOSMRequestToken]; + NSString * requestSecret = [ud stringForKey:kOSMRequestSecret]; + if (!requestToken || !requestSecret) { [[Statistics instance] logEvent:kStatEventName(kStatPlacePage, kStatEditTime) withParameters:@{kStatValue : kStatAuthorization}]; diff --git a/iphone/Maps/Maps.xcodeproj/project.pbxproj b/iphone/Maps/Maps.xcodeproj/project.pbxproj index 602074ae07..6d01b85fa6 100644 --- a/iphone/Maps/Maps.xcodeproj/project.pbxproj +++ b/iphone/Maps/Maps.xcodeproj/project.pbxproj @@ -5219,6 +5219,7 @@ "-ObjC", "-lopening_hours", "-lpugixml", + "-loauthcpp", "-leditor", ); PRODUCT_NAME = "maps.me dbg"; @@ -5341,6 +5342,7 @@ "-ObjC", "-lopening_hours", "-lpugixml", + "-loauthcpp", "-leditor", ); PRODUCT_NAME = "maps.me dbg"; @@ -5465,6 +5467,7 @@ "-ObjC", "-lopening_hours", "-lpugixml", + "-loauthcpp", "-leditor", ); PRODUCT_NAME = "maps.me beta"; @@ -5590,6 +5593,7 @@ "-ObjC", "-lopening_hours", "-lpugixml", + "-loauthcpp", "-leditor", ); PRODUCT_NAME = maps.me; @@ -5714,6 +5718,7 @@ "-ObjC", "-lopening_hours", "-lpugixml", + "-loauthcpp", "-leditor", ); PRODUCT_NAME = "maps.me rel"; @@ -5836,6 +5841,7 @@ "-ObjC", "-lopening_hours", "-lpugixml", + "-loauthcpp", "-leditor", ); PRODUCT_NAME = "maps.me rel";