Cloud storage: add Dropbox short-lived tokens (#9496)

reviewable/pr9597/r1
hius07 2 years ago committed by GitHub
parent 8a754cd271
commit 1ea7e16f3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,6 +11,7 @@ local InfoMessage = require("ui/widget/infomessage")
local InputDialog = require("ui/widget/inputdialog")
local LuaSettings = require("luasettings")
local Menu = require("ui/widget/menu")
local NetworkMgr = require("ui/network/manager")
local PathChooser = require("ui/widget/pathchooser")
local UIManager = require("ui/uimanager")
local WebDav = require("apps/cloudstorage/webdav")
@ -139,14 +140,30 @@ function CloudStorage:selectCloudType()
return true
end
function CloudStorage:generateDropBoxAccessToken()
if self.username or self.address == nil or self.address == "" then
-- short-lived token has been generated already in this session
-- or we have long-lived token in self.password
return true
else
local token = DropBox:getAccessToken(self.password, self.address)
if token then
self.password = token -- short-lived token
self.username = true -- flag
return true
end
end
end
function CloudStorage:openCloudServer(url)
local tbl, e
local NetworkMgr = require("ui/network/manager")
if self.type == "dropbox" then
if NetworkMgr:willRerunWhenOnline(function() self:openCloudServer(url) end) then
return
end
tbl, e = DropBox:run(url, self.password, self.choose_folder_mode)
if self:generateDropBoxAccessToken() then
tbl, e = DropBox:run(url, self.password, self.choose_folder_mode)
end
elseif self.type == "ftp" then
if NetworkMgr:willRerunWhenConnected(function() self:openCloudServer(url) end) then
return
@ -423,31 +440,38 @@ function CloudStorage:onMenuHold(item)
end
function CloudStorage:synchronizeCloud(item)
if NetworkMgr:willRerunWhenOnline(function() self:synchronizeCloud(item) end) then
return
end
self.password = item.password
self.address = item.address
local Trapper = require("ui/trapper")
Trapper:wrap(function()
Trapper:setPausedText("Download paused.\nDo you want to continue or abort downloading files?")
local ok, downloaded_files, failed_files = pcall(self.downloadListFiles, self, item)
if ok and downloaded_files then
if not failed_files then failed_files = 0 end
local text
if downloaded_files == 0 and failed_files == 0 then
text = _("No files to download from Dropbox.")
else
text = T(N_("Successfully downloaded 1 file from Dropbox to local storage.", "Successfully downloaded %1 files from Dropbox to local storage.", downloaded_files), downloaded_files)
if failed_files > 0 then
text = text .. "\n" .. T(N_("Failed to download 1 file.", "Failed to download %1 files.", failed_files), failed_files)
if self:generateDropBoxAccessToken() then
local ok, downloaded_files, failed_files = pcall(self.downloadListFiles, self, item)
if ok and downloaded_files then
if not failed_files then failed_files = 0 end
local text
if downloaded_files == 0 and failed_files == 0 then
text = _("No files to download from Dropbox.")
else
text = T(N_("Successfully downloaded 1 file from Dropbox to local storage.", "Successfully downloaded %1 files from Dropbox to local storage.", downloaded_files), downloaded_files)
if failed_files > 0 then
text = text .. "\n" .. T(N_("Failed to download 1 file.", "Failed to download %1 files.", failed_files), failed_files)
end
end
UIManager:show(InfoMessage:new{
text = text,
timeout = 3,
})
else
Trapper:reset() -- close any last widget not cleaned if error
UIManager:show(InfoMessage:new{
text = _("No files to download from Dropbox.\nPlease check your configuration and connection."),
timeout = 3,
})
end
UIManager:show(InfoMessage:new{
text = text,
timeout = 3,
})
else
Trapper:reset() -- close any last widget not cleaned if error
UIManager:show(InfoMessage:new{
text = _("No files to download from Dropbox.\nPlease check your configuration and connection."),
timeout = 3,
})
end
end)
end
@ -468,7 +492,7 @@ function CloudStorage:downloadListFiles(item)
end
end
end
local remote_files = DropBox:showFiles(item.sync_source_folder, item.password)
local remote_files = DropBox:showFiles(item.sync_source_folder, self.password)
if #remote_files == 0 then
UI:clear()
return false
@ -499,7 +523,7 @@ function CloudStorage:downloadListFiles(item)
if not go_on then
break
end
response = DropBox:downloadFileNoUI(file.url, item.password, item.sync_dest_folder .. "/" .. file.text)
response = DropBox:downloadFileNoUI(file.url, self.password, item.sync_dest_folder .. "/" .. file.text)
if response then
success_files = success_files + 1
else
@ -686,6 +710,7 @@ function CloudStorage:configCloud(type)
table.insert(cs_servers,{
name = fields[1],
password = fields[2],
address = fields[3],
type = "dropbox",
url = "/"
})
@ -732,6 +757,7 @@ function CloudStorage:editCloudServer(item)
if server.name == updated_config.text and server.password == updated_config.password then
server.name = fields[1]
server.password = fields[2]
server.address = fields[3]
cs_servers[i] = server
break
end
@ -790,7 +816,15 @@ end
function CloudStorage:infoServer(item)
if item.type == "dropbox" then
DropBox:info(item.password)
if NetworkMgr:willRerunWhenOnline(function() self:infoServer(item) end) then
return
end
self.password = item.password
self.address = item.address
if self:generateDropBoxAccessToken() then
DropBox:info(self.password)
self.username = nil
end
elseif item.type == "ftp" then
Ftp:info(item)
elseif item.type == "webdav" then

@ -12,6 +12,10 @@ local _ = require("gettext")
local DropBox = {}
function DropBox:getAccessToken(refresh_token, app_key_colon_secret)
return DropBoxApi:getAccessToken(refresh_token, app_key_colon_secret)
end
function DropBox:run(url, password, choose_folder_mode)
return DropBoxApi:listFolder(url, password, choose_folder_mode)
end
@ -92,39 +96,31 @@ function DropBox:createFolder(url, password, folder_name, callback_close)
end
function DropBox:config(item, callback)
local text_info = "How to generate Access Token:\n"..
"1. Open the following URL in your Browser, and log in using your account: https://www.dropbox.com/developers/apps.\n"..
"2. Click on >>Create App<<, then select >>Dropbox API app<<.\n"..
"3. Now go on with the configuration, choosing the app permissions and access restrictions to your DropBox folder.\n"..
"4. Enter the >>App Name<< that you prefer (e.g. KOReader).\n"..
"5. Now, click on the >>Create App<< button.\n" ..
"6. When your new App is successfully created, please click on the Generate button.\n"..
"7. Under the 'Generated access token' section, then enter code in Dropbox token field."
local hint_top = _("Your Dropbox name")
local text_top = ""
local hint_bottom = _("Dropbox token\n\n\n\n")
local text_bottom = ""
local title
local text_button_right = _("Add")
local text_info = _([[
Dropbox access tokens are short-lived (4 hours).
To generate new access token please use Dropbox refresh token and <APP_KEY>:<APP_SECRET> Base64 encoded string.
Some of the previously generated long-lived tokens are still valid.]])
local text_name, text_token, text_appkey
if item then
title = _("Edit Dropbox account")
text_button_right = _("Apply")
text_top = item.text
text_bottom = item.password
else
title = _("Add Dropbox account")
text_name = item.text
text_token = item.password
text_appkey = item.address
end
self.settings_dialog = MultiInputDialog:new {
title = title,
title = _("Dropbox cloud storage"),
fields = {
{
text = text_top,
hint = hint_top ,
text = text_name,
hint = _("Cloud storage displayed name"),
},
{
text = text_token,
hint = _("Dropbox refresh token\nor long-lived token (deprecated)"),
},
{
text = text_bottom,
hint = hint_bottom,
scroll = false,
text = text_appkey,
hint = _("Dropbox <APP_KEY>:<APP_SECRET>\n(leave blank for long-lived token)"),
},
},
buttons = {
@ -144,29 +140,20 @@ function DropBox:config(item, callback)
end
},
{
text = text_button_right,
text = _("Save"),
callback = function()
local fields = MultiInputDialog:getFields()
if fields[1] ~= "" and fields[2] ~= "" then
if item then
--edit
callback(item, fields)
else
-- add new
callback(fields)
end
self.settings_dialog:onClose()
UIManager:close(self.settings_dialog)
if item then
callback(item, fields)
else
UIManager:show(InfoMessage:new{
text = _("Please fill in all fields.")
})
callback(fields)
end
self.settings_dialog:onClose()
UIManager:close(self.settings_dialog)
end
},
},
},
input_type = "text",
}
UIManager:show(self.settings_dialog)
self.settings_dialog:onShowKeyboard()
@ -174,14 +161,17 @@ end
function DropBox:info(token)
local info = DropBoxApi:fetchInfo(token)
local info_text
if info and info.name then
info_text = T(_"Type: %1\nName: %2\nEmail: %3\nCountry: %4",
"Dropbox",info.name.display_name, info.email, info.country)
else
info_text = _("No information available")
local space_usage = DropBoxApi:fetchInfo(token, true)
if info and space_usage then
local account_type = info.account_type and info.account_type[".tag"]
local name = info.name and info.name.display_name
local space_total = space_usage.allocation and space_usage.allocation.allocated
UIManager:show(InfoMessage:new{
text = T(_"Type: %1\nName: %2\nEmail: %3\nCountry: %4\nSpace total: %5\nSpace used: %6",
account_type, name, info.email, info.country,
util.getFriendlySize(space_total), util.getFriendlySize(space_usage.used)),
})
end
UIManager:show(InfoMessage:new{text = info_text})
end
return DropBox

@ -13,36 +13,64 @@ local _ = require("gettext")
local DropBoxApi = {
}
local API_TOKEN = "https://api.dropbox.com/oauth2/token"
local API_URL_INFO = "https://api.dropboxapi.com/2/users/get_current_account"
local API_GET_SPACE_USAGE = "https://api.dropboxapi.com/2/users/get_space_usage"
local API_LIST_FOLDER = "https://api.dropboxapi.com/2/files/list_folder"
local API_DOWNLOAD_FILE = "https://content.dropboxapi.com/2/files/download"
local API_UPLOAD_FILE = "https://content.dropboxapi.com/2/files/upload"
local API_CREATE_FOLDER = "https://api.dropboxapi.com/2/files/create_folder_v2"
local API_LIST_ADD_FOLDER = "https://api.dropboxapi.com/2/files/list_folder/continue"
function DropBoxApi:fetchInfo(token)
function DropBoxApi:getAccessToken(refresh_token, app_key_colon_secret)
local sink = {}
local data = "grant_type=refresh_token&refresh_token=" .. refresh_token
local request = {
url = API_TOKEN,
method = "POST",
headers = {
["Authorization"] = "Basic " .. require("ffi/sha2").bin_to_base64(app_key_colon_secret),
["Content-Type"] = "application/x-www-form-urlencoded",
["Content-Length"] = string.len(data),
},
source = ltn12.source.string(data),
sink = ltn12.sink.table(sink),
}
socketutil:set_timeout()
local code = socket.skip(1, http.request(request))
socketutil:reset_timeout()
if code == 200 then
local headers = table.concat(sink)
if headers ~= "" then
local _, result = pcall(JSON.decode, headers)
return result["access_token"]
end
end
logger.info("Dropbox: cannot get access token")
end
function DropBoxApi:fetchInfo(token, space_usage)
local url = space_usage and API_GET_SPACE_USAGE or API_URL_INFO
local sink = {}
local request = {
url = API_URL_INFO,
url = url,
method = "POST",
headers = {
["Authorization"] = "Bearer " .. token,
},
sink = ltn12.sink.table(sink),
}
local headers_request = socket.skip(1, http.request(request))
socketutil:set_timeout()
local code = socket.skip(1, http.request(request))
socketutil:reset_timeout()
local result_response = table.concat(sink)
if headers_request == nil then
return nil
end
if result_response ~= "" then
local _, result = pcall(JSON.decode, result_response)
return result
else
return nil
if code == 200 then
local headers = table.concat(sink)
if headers ~= "" then
local _, result = pcall(JSON.decode, headers)
return result
end
end
logger.info("Dropbox: cannot get account info")
end
function DropBoxApi:fetchListFolders(path, token)

Loading…
Cancel
Save