/*** * ==++== * * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ==--== * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * * live_connect.h * * Simple API for connecting to Windows Live services, such as OneDrive and Hotmail. * Only supported for App Store apps. * * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ****/ #pragma once #include #include "cpprest/http_client.h" #include "cpprest/streams.h" #include "cpprest/filestream.h" namespace web { namespace live { #if defined(__cplusplus_winrt) /// /// This namespace contains symbols for the standard Windows Live permission scopes, which /// determine what privileges the application has to access data. What operations are allowed /// and what details are returned will depend on privileges requested and granted. /// /// See MSDN documentation for Live Connect at http://msdn.microsoft.com/en-us/live/ for details /// on the precise meaning of these scopes. /// namespace scopes { static const utility::string_t wl_birthday = U("wl.birthday"); static const utility::string_t wl_basic = U("wl.basic"); static const utility::string_t wl_calendars = U("wl.calendars"); static const utility::string_t wl_calendars_update = U("wl.calendars_update"); static const utility::string_t wl_contacts_birthday = U("wl.contacts_birthday"); static const utility::string_t wl_contacts_create = U("wl.contacts_create"); static const utility::string_t wl_contacts_calendars = U("wl.contacts_calendars"); static const utility::string_t wl_contacts_photos = U("wl.contacts_photos"); static const utility::string_t wl_contacts_skydrive = U("wl.contacts_skydrive"); static const utility::string_t wl_emails = U("wl.emails"); static const utility::string_t wl_events_create = U("wl.events_create"); static const utility::string_t wl_messenger = U("wl.messenger"); static const utility::string_t wl_offline_access = U("wl.offline_access"); static const utility::string_t wl_phone_numbers = U("wl.phone_numbers"); static const utility::string_t wl_photos = U("wl.photos"); static const utility::string_t wl_postal_addresses = U("wl.postal_addresses"); static const utility::string_t wl_share = U("wl.share"); static const utility::string_t wl_signin = U("wl.signin"); static const utility::string_t wl_skydrive = U("wl.skydrive"); static const utility::string_t wl_skydrive_update = U("wl.skydrive_update"); static const utility::string_t wl_work_profile = U("wl.work_profile"); } /// /// Represents a session connected to Windows Live services like Calendar, Contacts, OneDrive, and the /// user profile, by using the Live Connect REST API. It is a thin layer on top of the Casablanca HTTP /// library, tailored to the usage scenarios for Windows Live clients. /// /// /// See http://msdn.microsoft.com/onedrive/ for details on using the OneDrive REST API. /// /// Note: when passing resource paths into the functions in this class, it is not necessary to add the /// leading '/' path character. It will be added automatically when constructing the HTTP request. /// /// Most Windows Live requests will return data in the form of plain text or JSON. The APIs in this /// class will typically return data as JSON. The most significant exception to this is download(), /// which will not return any data to be extracted, it will place the result in the stream or file /// passed into it. Also, remove() will return data only to provide error information: for non-error /// cases, the JSON value will be null. /// /// This class relies on PPL tasks to represent asynchrony: the examples contained within the comments /// show synchronous forms of invoking these APIs. In real use, applications should be relying on /// .then() instead of .get() or .wait(). /// class live_client { public: /// /// Constructs a new Windows Live client /// live_client() : m_client(L"https://apis.live.net/v5.0/"), m_authenticator(ref new Windows::Security::Authentication::OnlineId::OnlineIdAuthenticator) { } /// /// Authenticates the current user and requests permission to access a listed Live services. /// /// A string containing a space-separated list of access scopes to request /// true if authentication succeeded, false otherwise. pplx::task login(utility::string_t scopes) { this->m_token.clear(); auto request = ref new Windows::Security::Authentication::OnlineId::OnlineIdServiceTicketRequest(ref new Platform::String(scopes.c_str()), "DELEGATION"); return pplx::create_task(m_authenticator->AuthenticateUserAsync(request)) .then([this](Windows::Security::Authentication::OnlineId::UserIdentity^ ident) { if ( ident->Tickets->Size > 0 ) { auto ticket = ident->Tickets->GetAt(0); m_token = std::wstring(ticket->Value->Data()); return true; } return false; }); } /// /// Authenticates the current user and requests permission to access a listed Live services. /// /// /// The starting point of an iterator over a std::string collection, (for example a vector of /// strings), holding the names of scopes to get permission for. /// /// The end point for the same collection. /// true if authentication succeeded, false otherwise. template pplx::task login(Iter begin, Iter end) { utility::string_t services; for (Iter iter = begin; iter != end; ++iter) { services.append(*iter); services.append(utility::string_t(L" ")); } return login(services); } /// /// Dismisses any and all permissions that have been granted to the user. /// /// true if logout succeeded, false otherwise. /// /// Whether the logout attempt was successful or not, the application will /// be required to log in again, since the access token will be cleared. /// pplx::task logout() { this->m_token.clear(); if (!m_authenticator->CanSignOut) return pplx::task_from_result(false); return pplx::create_task(m_authenticator->SignOutUserAsync()).then([this] { return true; }); } /// /// Retrieves the access token in use by this client instance. /// /// The current token: a string. An invalid token is indicated by an empty string. const utility::string_t& access_token() const { return m_token; } /// /// Retrieves data from Windows Live. /// /// The Windows Live resource to retrieve. /// The JSON value resulting from the request. /// To get the current user profile: json::value profile = live_clnt.get(L"me").get().extract_json().get(); pplx::task get(const utility::string_t& resource) { return _make_request(web::http::methods::GET, resource).then([](web::http::http_response response) { return _json_extract(response); }); } /// /// Deletes data from Windows Live. /// /// The Windows Live resource to delete. /// The JSON value resulting from the request. /// To delete a file: live_clnt.remove(L"file.NNNNNNNNNNNN").wait();, where file.NNNNNNNNNNNN is a file identifier. pplx::task remove(const utility::string_t& resource) { return _make_request(web::http::methods::DEL, resource).then([](web::http::http_response response) { return _json_extract(response); }); } /// /// Modifies data in Windows Live. /// /// The Windows Live resource to modify. /// The JSON value resulting from the request. /// In most cases, the response will contain data about the modified resource. pplx::task put(const utility::string_t& resource, const web::json::value& data) { return _make_request(web::http::methods::PUT, resource, data).then([](web::http::http_response response) { return _json_extract(response); }); } /// /// Adds data to Windows Live. /// /// The Windows Live resource (such as a folder) where data should be added. /// The JSON value resulting from the request. /// In most cases, the response will contain data about the added resource. /// To add a contact: json::value contact = ...; live_clnt.post(L"me/contacts", contact).wait(); pplx::task post(const utility::string_t& resource, const web::json::value& data) { return _make_request(web::http::methods::POST, resource, data).then([](web::http::http_response response) { return _json_extract(response); }); } /// /// Copies data in Windows Live. /// /// The Windows Live resource to copy. /// The location where the resource copy should be placed. /// The JSON value resulting from the request. /// In most cases, the response will contain data about the added resource. /// To copy a file: live_clnt.copy(L"file.NNNNNNNNNNNN", L"folder.MMMMMMMMMMMM").wait(); pplx::task copy(const utility::string_t& resource, const utility::string_t& destination) { return _make_request(U("COPY"), resource, destination).then([](web::http::http_response response) { return _json_extract(response); }); } /// /// Moves data in Windows Live. /// /// The Windows Live resource to move. /// The location where the resource should be placed. /// The JSON value resulting from the request. /// In most cases, the response will contain data about the resource in its new location. /// To copy a file: live_clnt.copy(L"file.NNNNNNNNNNNN", L"folder.MMMMMMMMMMMM").wait(); pplx::task move(const utility::string_t& resource, const utility::string_t& destination) { return _make_request(U("MOVE"), resource, destination).then([](web::http::http_response response) { return _json_extract(response); }); } /// /// Download a file from OneDrive. /// /// The OneDrive file id to download. /// A stream into which the contents of the file should be placed. /// The size of the downloaded resource. /// /// To download a file: ostream stream = ...; live_clnt.download(L"file.NNNNNNNNNNNN", ostream).get().content_ready().wait(); pplx::task download(const utility::string_t& file_id, concurrency::streams::ostream stream) { web::http::uri_builder bldr; bldr.append(file_id); bldr.append_path(U("content")); web::http::http_request req(web::http::methods::GET); req.set_request_uri(bldr.to_string()); return _make_request(req) .then([stream](web::http::http_response response) -> pplx::task { if ( response.status_code() >= 400 ) { return response.extract_string().then( [](utility::string_t message) -> pplx::task { return pplx::task_from_exception(std::exception(utility::conversions::to_utf8string(message).c_str())); }); } return response.body().read_to_end(stream.streambuf()); }); } /// /// Download a file from OneDrive. /// /// The OneDrive file id to download. /// A StorageFile reference identifying the target for the downloaded data. /// The size of the downloaded resource. /// pplx::task download(const utility::string_t& file_id, Windows::Storage::StorageFile^ file) { if ( file == nullptr ) throw std::invalid_argument("file reference cannot be null"); return concurrency::streams::file_stream::open_ostream(file).then( [file_id,this](concurrency::streams::ostream stream) { web::http::uri_builder bldr; bldr.append(file_id); bldr.append_path(U("content")); web::http::http_request req(web::http::methods::GET); req.set_request_uri(bldr.to_string()); return _make_request(req) .then([stream](web::http::http_response response) -> pplx::task { if ( response.status_code() >= 400 ) { return response.extract_string().then( [](utility::string_t message) -> pplx::task { return pplx::task_from_exception(std::exception(utility::conversions::to_utf8string(message).c_str())); }); } return response.body().read_to_end(stream.streambuf()); }) .then([stream](pplx::task ret_task) { return stream.flush().then([stream, ret_task]() { return stream.close(); }).then([ret_task]() { return ret_task; }); }); }); } /// /// Upload a file to OneDrive. /// /// The path of the file location in OneDrive. It should be of the form "folder.NNNNNNNNNN/files/file_name.ext" /// A stream from which data for the file will be read. /// The size of the data to upload. /// The JSON value resulting from the request, containing metadata about the uploaded file. /// /// The stream must contain at least as many bytes as indicated by 'content_length', starting from its current read position. /// pplx::task upload(const utility::string_t& path, concurrency::streams::istream stream, size_t content_length) { web::http::uri_builder bldr; bldr.append(path); web::http::http_request req(web::http::methods::PUT); req.set_request_uri(bldr.to_string()); req.set_body(stream, content_length, U("")); return _make_request(req) .then([](web::http::http_response response) { return _json_extract(response); }); } /// /// Upload a file to OneDrive. /// /// The path of the file location in OneDrive. It should be of the form "folder.NNNNNNNNNN/files/file_name.ext" /// A StorageFile reference identifying the source of the uploaded data. /// The size of the data to upload. /// The JSON value resulting from the request, containing metadata about the uploaded file. /// The entire file will be uploaded. pplx::task upload(const utility::string_t& path, Windows::Storage::StorageFile^ file) { if ( file == nullptr ) throw std::invalid_argument("file reference cannot be null"); return pplx::create_task(file->GetBasicPropertiesAsync()).then( [path,this,file](Windows::Storage::FileProperties::BasicProperties^ props) { if (props == nullptr) throw std::exception("failed to retrieve file properties; cannot determine its size"); size_t size = (size_t)props->Size; return concurrency::streams::file_stream::open_istream(file).then( [path,this,size](concurrency::streams::istream stream) { web::http::uri_builder bldr; bldr.append(path); web::http::http_request req(web::http::methods::PUT); req.set_request_uri(bldr.to_string()); req.set_body(stream, size, U("")); return _make_request(req) .then([](web::http::http_response response) { return response.content_ready(); }) .then([stream](pplx::task response) { return stream.close().then([response]() { return _json_extract(response.get()); }); }); }); }); } private: static pplx::task _json_extract(web::http::http_response response) { switch(response.status_code()) { case web::http::status_codes::NoContent: return pplx::task_from_result(web::json::value::null()); default: if ( response.status_code() >= 400 ) { return response.extract_string().then( [](utility::string_t message) -> pplx::task { return pplx::task_from_exception(std::exception(utility::conversions::to_utf8string(message).c_str())); }); } return response.extract_json(); } } pplx::task _make_request(web::http::method method, const utility::string_t& path) { web::http::uri_builder bldr; bldr.append(path); web::http::http_request req(method); req.set_request_uri(bldr.to_string()); return _make_request(req); } pplx::task _make_request(web::http::method method, const utility::string_t& path, const web::json::value& data) { web::http::uri_builder bldr; bldr.append(path); web::http::http_request req(method); req.set_request_uri(bldr.to_string()); req.set_body(data); return _make_request(req); } pplx::task _make_request(web::http::method method, const utility::string_t& path, const utility::string_t& destination) { web::http::uri_builder bldr; bldr.append(path); web::json::value data; data[U("destination")] = web::json::value::string(destination); web::http::http_request req(method); req.set_request_uri(bldr.to_string()); req.set_body(data); return _make_request(req); } pplx::task _make_request(web::http::http_request req) { if (!m_token.empty()) req.headers().add(U("Authorization"), U("Bearer ")+m_token); return m_client.request(req); } Windows::Security::Authentication::OnlineId::OnlineIdAuthenticator^ m_authenticator; web::http::client::http_client m_client; utility::string_t m_token; }; #endif }}