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 InputDialog = require("ui/widget/inputdialog")
local LuaSettings = require("luasettings") local LuaSettings = require("luasettings")
local Menu = require("ui/widget/menu") local Menu = require("ui/widget/menu")
local NetworkMgr = require("ui/network/manager")
local PathChooser = require("ui/widget/pathchooser") local PathChooser = require("ui/widget/pathchooser")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local WebDav = require("apps/cloudstorage/webdav") local WebDav = require("apps/cloudstorage/webdav")
@ -139,14 +140,30 @@ function CloudStorage:selectCloudType()
return true return true
end 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) function CloudStorage:openCloudServer(url)
local tbl, e local tbl, e
local NetworkMgr = require("ui/network/manager")
if self.type == "dropbox" then if self.type == "dropbox" then
if NetworkMgr:willRerunWhenOnline(function() self:openCloudServer(url) end) then if NetworkMgr:willRerunWhenOnline(function() self:openCloudServer(url) end) then
return return
end 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 elseif self.type == "ftp" then
if NetworkMgr:willRerunWhenConnected(function() self:openCloudServer(url) end) then if NetworkMgr:willRerunWhenConnected(function() self:openCloudServer(url) end) then
return return
@ -423,31 +440,38 @@ function CloudStorage:onMenuHold(item)
end end
function CloudStorage:synchronizeCloud(item) 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") local Trapper = require("ui/trapper")
Trapper:wrap(function() Trapper:wrap(function()
Trapper:setPausedText("Download paused.\nDo you want to continue or abort downloading files?") 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 self:generateDropBoxAccessToken() then
if ok and downloaded_files then local ok, downloaded_files, failed_files = pcall(self.downloadListFiles, self, item)
if not failed_files then failed_files = 0 end if ok and downloaded_files then
local text if not failed_files then failed_files = 0 end
if downloaded_files == 0 and failed_files == 0 then local text
text = _("No files to download from Dropbox.") if downloaded_files == 0 and failed_files == 0 then
else text = _("No files to download from Dropbox.")
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) else
if failed_files > 0 then 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)
text = text .. "\n" .. T(N_("Failed to download 1 file.", "Failed to download %1 files.", failed_files), failed_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 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
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) end)
end end
@ -468,7 +492,7 @@ function CloudStorage:downloadListFiles(item)
end end
end 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 if #remote_files == 0 then
UI:clear() UI:clear()
return false return false
@ -499,7 +523,7 @@ function CloudStorage:downloadListFiles(item)
if not go_on then if not go_on then
break break
end 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 if response then
success_files = success_files + 1 success_files = success_files + 1
else else
@ -686,6 +710,7 @@ function CloudStorage:configCloud(type)
table.insert(cs_servers,{ table.insert(cs_servers,{
name = fields[1], name = fields[1],
password = fields[2], password = fields[2],
address = fields[3],
type = "dropbox", type = "dropbox",
url = "/" url = "/"
}) })
@ -732,6 +757,7 @@ function CloudStorage:editCloudServer(item)
if server.name == updated_config.text and server.password == updated_config.password then if server.name == updated_config.text and server.password == updated_config.password then
server.name = fields[1] server.name = fields[1]
server.password = fields[2] server.password = fields[2]
server.address = fields[3]
cs_servers[i] = server cs_servers[i] = server
break break
end end
@ -790,7 +816,15 @@ end
function CloudStorage:infoServer(item) function CloudStorage:infoServer(item)
if item.type == "dropbox" then 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 elseif item.type == "ftp" then
Ftp:info(item) Ftp:info(item)
elseif item.type == "webdav" then elseif item.type == "webdav" then

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

@ -13,36 +13,64 @@ local _ = require("gettext")
local DropBoxApi = { 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_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_LIST_FOLDER = "https://api.dropboxapi.com/2/files/list_folder"
local API_DOWNLOAD_FILE = "https://content.dropboxapi.com/2/files/download" local API_DOWNLOAD_FILE = "https://content.dropboxapi.com/2/files/download"
local API_UPLOAD_FILE = "https://content.dropboxapi.com/2/files/upload" 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_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" 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 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() 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 = { local request = {
url = API_URL_INFO, url = url,
method = "POST", method = "POST",
headers = { headers = {
["Authorization"] = "Bearer " .. token, ["Authorization"] = "Bearer " .. token,
}, },
sink = ltn12.sink.table(sink), 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() socketutil:reset_timeout()
local result_response = table.concat(sink) if code == 200 then
if headers_request == nil then local headers = table.concat(sink)
return nil if headers ~= "" then
end local _, result = pcall(JSON.decode, headers)
if result_response ~= "" then return result
local _, result = pcall(JSON.decode, result_response) end
return result
else
return nil
end end
logger.info("Dropbox: cannot get account info")
end end
function DropBoxApi:fetchListFolders(path, token) function DropBoxApi:fetchListFolders(path, token)

Loading…
Cancel
Save