00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 #define FORBIDDEN_SYMBOL_ALLOW_ALL
00024
00025 #include <curl/curl.h>
00026 #include "backends/cloud/box/boxstorage.h"
00027 #include "backends/cloud/box/boxlistdirectorybyidrequest.h"
00028 #include "backends/cloud/box/boxtokenrefresher.h"
00029 #include "backends/cloud/box/boxuploadrequest.h"
00030 #include "backends/cloud/cloudmanager.h"
00031 #include "backends/networking/curl/connectionmanager.h"
00032 #include "backends/networking/curl/curljsonrequest.h"
00033 #include "backends/networking/curl/networkreadstream.h"
00034 #include "common/config-manager.h"
00035 #include "common/debug.h"
00036 #include "common/json.h"
00037
00038 #ifdef ENABLE_RELEASE
00039 #include "dists/clouds/cloud_keys.h"
00040 #endif
00041
00042 namespace Cloud {
00043 namespace Box {
00044
00045 #define BOX_OAUTH2_TOKEN "https://api.box.com/oauth2/token"
00046 #define BOX_API_FOLDERS "https://api.box.com/2.0/folders"
00047 #define BOX_API_FILES_CONTENT "https://api.box.com/2.0/files/%s/content"
00048 #define BOX_API_USERS_ME "https://api.box.com/2.0/users/me"
00049
00050 char *BoxStorage::KEY = nullptr;
00051 char *BoxStorage::SECRET = nullptr;
00052
00053 void BoxStorage::loadKeyAndSecret() {
00054 #ifdef ENABLE_RELEASE
00055 KEY = RELEASE_BOX_KEY;
00056 SECRET = RELEASE_BOX_SECRET;
00057 #else
00058 Common::String k = ConfMan.get("BOX_KEY", ConfMan.kCloudDomain);
00059 KEY = new char[k.size() + 1];
00060 memcpy(KEY, k.c_str(), k.size());
00061 KEY[k.size()] = 0;
00062
00063 k = ConfMan.get("BOX_SECRET", ConfMan.kCloudDomain);
00064 SECRET = new char[k.size() + 1];
00065 memcpy(SECRET, k.c_str(), k.size());
00066 SECRET[k.size()] = 0;
00067 #endif
00068 }
00069
00070 BoxStorage::BoxStorage(Common::String token, Common::String refreshToken):
00071 _token(token), _refreshToken(refreshToken) {}
00072
00073 BoxStorage::BoxStorage(Common::String code) {
00074 getAccessToken(
00075 new Common::Callback<BoxStorage, BoolResponse>(this, &BoxStorage::codeFlowComplete),
00076 new Common::Callback<BoxStorage, Networking::ErrorResponse>(this, &BoxStorage::codeFlowFailed),
00077 code
00078 );
00079 }
00080
00081 BoxStorage::~BoxStorage() {}
00082
00083 void BoxStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
00084 if (!KEY || !SECRET)
00085 loadKeyAndSecret();
00086 bool codeFlow = (code != "");
00087
00088 if (!codeFlow && _refreshToken == "") {
00089 warning("BoxStorage: no refresh token available to get new access token.");
00090 if (callback) (*callback)(BoolResponse(nullptr, false));
00091 return;
00092 }
00093
00094 Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::tokenRefreshed, callback);
00095 if (errorCallback == nullptr)
00096 errorCallback = getErrorPrintingCallback();
00097
00098 Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, BOX_OAUTH2_TOKEN);
00099 if (codeFlow) {
00100 request->addPostField("grant_type=authorization_code");
00101 request->addPostField("code=" + code);
00102 } else {
00103 request->addPostField("grant_type=refresh_token");
00104 request->addPostField("refresh_token=" + _refreshToken);
00105 }
00106 request->addPostField("client_id=" + Common::String(KEY));
00107 request->addPostField("client_secret=" + Common::String(SECRET));
00108
00109
00110
00111
00112
00113
00114
00115 addRequest(request);
00116 }
00117
00118 void BoxStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) {
00119 Common::JSONValue *json = response.value;
00120 if (!json) {
00121 warning("BoxStorage: got NULL instead of JSON");
00122 if (callback)
00123 (*callback)(BoolResponse(nullptr, false));
00124 delete callback;
00125 return;
00126 }
00127
00128 if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage")) {
00129 if (callback)
00130 (*callback)(BoolResponse(nullptr, false));
00131 delete json;
00132 delete callback;
00133 return;
00134 }
00135
00136 Common::JSONObject result = json->asObject();
00137 if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "BoxStorage") ||
00138 !Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "BoxStorage")) {
00139 warning("BoxStorage: bad response, no token passed");
00140 debug(9, "%s", json->stringify().c_str());
00141 if (callback)
00142 (*callback)(BoolResponse(nullptr, false));
00143 } else {
00144 _token = result.getVal("access_token")->asString();
00145 _refreshToken = result.getVal("refresh_token")->asString();
00146 CloudMan.save();
00147 if (callback)
00148 (*callback)(BoolResponse(nullptr, true));
00149 }
00150 delete json;
00151 delete callback;
00152 }
00153
00154 void BoxStorage::codeFlowComplete(BoolResponse response) {
00155 if (!response.value) {
00156 warning("BoxStorage: failed to get access token through code flow");
00157 CloudMan.removeStorage(this);
00158 return;
00159 }
00160
00161 CloudMan.replaceStorage(this, kStorageBoxId);
00162 ConfMan.flushToDisk();
00163 }
00164
00165 void BoxStorage::codeFlowFailed(Networking::ErrorResponse error) {
00166 debug(9, "BoxStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
00167 debug(9, "%s", error.response.c_str());
00168 CloudMan.removeStorage(this);
00169 }
00170
00171 void BoxStorage::saveConfig(Common::String keyPrefix) {
00172 ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
00173 ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
00174 }
00175
00176 Common::String BoxStorage::name() const {
00177 return "Box";
00178 }
00179
00180 void BoxStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) {
00181 Common::JSONValue *json = response.value;
00182 if (!json) {
00183 warning("BoxStorage::infoInnerCallback: NULL passed instead of JSON");
00184 delete outerCallback;
00185 return;
00186 }
00187
00188 if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::infoInnerCallback")) {
00189 delete json;
00190 delete outerCallback;
00191 return;
00192 }
00193
00194 Common::JSONObject jsonInfo = json->asObject();
00195
00196 Common::String uid, displayName, email;
00197 uint64 quotaUsed = 0, quotaAllocated = 0;
00198
00199
00200
00201
00202 if (Networking::CurlJsonRequest::jsonContainsString(jsonInfo, "id", "BoxStorage::infoInnerCallback"))
00203 uid = jsonInfo.getVal("id")->asString();
00204
00205 if (Networking::CurlJsonRequest::jsonContainsString(jsonInfo, "name", "BoxStorage::infoInnerCallback"))
00206 displayName = jsonInfo.getVal("name")->asString();
00207
00208 if (Networking::CurlJsonRequest::jsonContainsString(jsonInfo, "login", "BoxStorage::infoInnerCallback"))
00209 email = jsonInfo.getVal("login")->asString();
00210
00211 if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(jsonInfo, "space_amount", "BoxStorage::infoInnerCallback"))
00212 quotaAllocated = jsonInfo.getVal("space_amount")->asIntegerNumber();
00213
00214 if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(jsonInfo, "space_used", "BoxStorage::infoInnerCallback"))
00215 quotaUsed = jsonInfo.getVal("space_used")->asIntegerNumber();
00216
00217 Common::String username = email;
00218 if (username == "") username = displayName;
00219 if (username == "") username = uid;
00220 CloudMan.setStorageUsername(kStorageBoxId, username);
00221
00222 if (outerCallback) {
00223 (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, displayName, email, quotaUsed, quotaAllocated)));
00224 delete outerCallback;
00225 }
00226
00227 delete json;
00228 }
00229
00230 Networking::Request *BoxStorage::listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) {
00231 if (!errorCallback)
00232 errorCallback = getErrorPrintingCallback();
00233 if (!callback)
00234 callback = getPrintFilesCallback();
00235 return addRequest(new BoxListDirectoryByIdRequest(this, id, callback, errorCallback));
00236 }
00237
00238 void BoxStorage::createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response) {
00239 Common::JSONValue *json = response.value;
00240 if (!json) {
00241 warning("BoxStorage::createDirectoryInnerCallback: NULL passed instead of JSON");
00242 delete outerCallback;
00243 return;
00244 }
00245
00246 if (outerCallback) {
00247 if (Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::createDirectoryInnerCallback")) {
00248 Common::JSONObject jsonInfo = json->asObject();
00249 (*outerCallback)(BoolResponse(nullptr, jsonInfo.contains("id")));
00250 } else {
00251 (*outerCallback)(BoolResponse(nullptr, false));
00252 }
00253 delete outerCallback;
00254 }
00255
00256 delete json;
00257 }
00258
00259 Networking::Request *BoxStorage::createDirectoryWithParentId(Common::String parentId, Common::String directoryName, BoolCallback callback, Networking::ErrorCallback errorCallback) {
00260 if (!errorCallback)
00261 errorCallback = getErrorPrintingCallback();
00262
00263 Common::String url = BOX_API_FOLDERS;
00264 Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::createDirectoryInnerCallback, callback);
00265 Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, url.c_str());
00266 request->addHeader("Authorization: Bearer " + accessToken());
00267 request->addHeader("Content-Type: application/json");
00268
00269 Common::JSONObject parentObject;
00270 parentObject.setVal("id", new Common::JSONValue(parentId));
00271
00272 Common::JSONObject jsonRequestParameters;
00273 jsonRequestParameters.setVal("name", new Common::JSONValue(directoryName));
00274 jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject));
00275
00276 Common::JSONValue value(jsonRequestParameters);
00277 request->addPostField(Common::JSON::stringify(&value));
00278
00279 return addRequest(request);
00280 }
00281
00282 Networking::Request *BoxStorage::upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) {
00283 if (!errorCallback)
00284 errorCallback = getErrorPrintingCallback();
00285 return addRequest(new BoxUploadRequest(this, remotePath, localPath, callback, errorCallback));
00286 }
00287
00288 Networking::Request *BoxStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
00289 warning("BoxStorage::upload(ReadStream) not implemented");
00290 if (errorCallback)
00291 (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "BoxStorage::upload(ReadStream) not implemented", -1));
00292 delete callback;
00293 delete errorCallback;
00294 return nullptr;
00295 }
00296
00297 bool BoxStorage::uploadStreamSupported() {
00298 return false;
00299 }
00300
00301 Networking::Request *BoxStorage::streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
00302 if (callback) {
00303 Common::String url = Common::String::format(BOX_API_FILES_CONTENT, id.c_str());
00304 Common::String header = "Authorization: Bearer " + _token;
00305 curl_slist *headersList = curl_slist_append(nullptr, header.c_str());
00306 Networking::NetworkReadStream *stream = new Networking::NetworkReadStream(url.c_str(), headersList, "");
00307 (*callback)(Networking::NetworkReadStreamResponse(nullptr, stream));
00308 }
00309 delete callback;
00310 delete errorCallback;
00311 return nullptr;
00312 }
00313
00314 Networking::Request *BoxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
00315 Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, StorageInfoResponse, Networking::JsonResponse>(this, &BoxStorage::infoInnerCallback, callback);
00316 Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, BOX_API_USERS_ME);
00317 request->addHeader("Authorization: Bearer " + _token);
00318 return addRequest(request);
00319 }
00320
00321 Common::String BoxStorage::savesDirectoryPath() { return "scummvm/saves/"; }
00322
00323 BoxStorage *BoxStorage::loadFromConfig(Common::String keyPrefix) {
00324 loadKeyAndSecret();
00325
00326 if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
00327 warning("BoxStorage: no access_token found");
00328 return nullptr;
00329 }
00330
00331 if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
00332 warning("BoxStorage: no refresh_token found");
00333 return nullptr;
00334 }
00335
00336 Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
00337 Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
00338 return new BoxStorage(accessToken, refreshToken);
00339 }
00340
00341 Common::String BoxStorage::getRootDirectoryId() {
00342 return "0";
00343 }
00344
00345 }
00346 }