From 56b582cdb9e68c9941e90bae47f6144c45e82a8a Mon Sep 17 00:00:00 2001 From: Hunter Wittenborn Date: Fri, 24 Feb 2023 09:08:38 -0600 Subject: [PATCH] authorize: add support for custom templates This adds support for providing custom Go templates for use in the `rclone authorize` command. Fixes #6741 --- cmd/authorize/authorize.go | 10 +++++--- cmd/serve/dlna/data/assets_vfsdata.go | 1 + docs/content/commands/rclone_authorize.md | 7 +++++- fs/config/authorize.go | 7 +++++- fs/config/config.go | 6 +++++ lib/file/mkdir_windows_test.go | 4 ++-- lib/oauthutil/oauthutil.go | 29 ++++++++++++++++++++--- 7 files changed, 54 insertions(+), 10 deletions(-) diff --git a/cmd/authorize/authorize.go b/cmd/authorize/authorize.go index 84c0bc0f3..5d946880a 100644 --- a/cmd/authorize/authorize.go +++ b/cmd/authorize/authorize.go @@ -12,12 +12,14 @@ import ( var ( noAutoBrowser bool + template string ) func init() { cmd.Root.AddCommand(commandDefinition) cmdFlags := commandDefinition.Flags() flags.BoolVarP(cmdFlags, &noAutoBrowser, "auth-no-open-browser", "", false, "Do not automatically open auth link in default browser") + flags.StringVarP(cmdFlags, &template, "template", "", "", "The path to a custom Go template for generating HTML responses") } var commandDefinition = &cobra.Command{ @@ -28,13 +30,15 @@ Remote authorization. Used to authorize a remote or headless rclone from a machine with a browser - use as instructed by rclone config. -Use the --auth-no-open-browser to prevent rclone to open auth -link in default browser automatically.`, +Use --auth-no-open-browser to prevent rclone to open auth +link in default browser automatically. + +Use --template to generate HTML output via a custom Go template. If a blank string is provided as an argument to this flag, the default template is used.`, Annotations: map[string]string{ "versionIntroduced": "v1.27", }, RunE: func(command *cobra.Command, args []string) error { cmd.CheckArgs(1, 3, command, args) - return config.Authorize(context.Background(), args, noAutoBrowser) + return config.Authorize(context.Background(), args, noAutoBrowser, template) }, } diff --git a/cmd/serve/dlna/data/assets_vfsdata.go b/cmd/serve/dlna/data/assets_vfsdata.go index 84fb623ad..da6fd39ab 100644 --- a/cmd/serve/dlna/data/assets_vfsdata.go +++ b/cmd/serve/dlna/data/assets_vfsdata.go @@ -1,5 +1,6 @@ // Code generated by vfsgen; DO NOT EDIT. +//go:build !dev // +build !dev package data diff --git a/docs/content/commands/rclone_authorize.md b/docs/content/commands/rclone_authorize.md index 3620829eb..c976cec68 100644 --- a/docs/content/commands/rclone_authorize.md +++ b/docs/content/commands/rclone_authorize.md @@ -17,9 +17,13 @@ Remote authorization. Used to authorize a remote or headless rclone from a machine with a browser - use as instructed by rclone config. -Use the --auth-no-open-browser to prevent rclone to open auth +Use --auth-no-open-browser to prevent rclone to open auth link in default browser automatically. +Use --template to generate HTML output via a custom Go +template. If a blank string is provided as an argument to +this flag, the default template is used. + ``` rclone authorize [flags] ``` @@ -29,6 +33,7 @@ rclone authorize [flags] ``` --auth-no-open-browser Do not automatically open auth link in default browser -h, --help help for authorize + --template string Use a custom Go template for generating HTML responses ``` See the [global flags page](/flags/) for global options not listed here. diff --git a/fs/config/authorize.go b/fs/config/authorize.go index 99aa2c947..b40f7e2b4 100644 --- a/fs/config/authorize.go +++ b/fs/config/authorize.go @@ -15,7 +15,7 @@ import ( // rclone authorize "fs name" // rclone authorize "fs name" "base64 encoded JSON blob" // rclone authorize "fs name" "client id" "client secret" -func Authorize(ctx context.Context, args []string, noAutoBrowser bool) error { +func Authorize(ctx context.Context, args []string, noAutoBrowser bool, templateFile string) error { ctx = suppressConfirm(ctx) ctx = fs.ConfigOAuthOnly(ctx) switch len(args) { @@ -41,6 +41,11 @@ func Authorize(ctx context.Context, args []string, noAutoBrowser bool) error { inM[ConfigAuthNoBrowser] = "true" } + // Indicate if we specified a custom template via a file + if templateFile != "" { + inM[ConfigTemplateFile] = templateFile + } + // Add extra parameters if supplied if len(args) == 2 { err := inM.Decode(args[1]) diff --git a/fs/config/config.go b/fs/config/config.go index 4a6725237..b2a354037 100644 --- a/fs/config/config.go +++ b/fs/config/config.go @@ -58,6 +58,12 @@ const ( // ConfigAuthNoBrowser indicates that we do not want to open browser ConfigAuthNoBrowser = "config_auth_no_browser" + + // ConfigTemplate is the template content to be used in the authorization webserver + ConfigTemplate = "config_template" + + // ConfigTemplateFile is the path to a template file to read into the value of `ConfigTemplate` above + ConfigTemplateFile = "config_template_file" ) // Storage defines an interface for loading and saving config to diff --git a/lib/file/mkdir_windows_test.go b/lib/file/mkdir_windows_test.go index 29849d7f7..2750e2dc3 100644 --- a/lib/file/mkdir_windows_test.go +++ b/lib/file/mkdir_windows_test.go @@ -77,7 +77,7 @@ func unusedDrive(t *testing.T) string { return string(letter) + ":" } -func checkMkdirAll(t *testing.T, path string, valid bool, errormsgs... string) { +func checkMkdirAll(t *testing.T, path string, valid bool, errormsgs ...string) { if valid { assert.NoError(t, MkdirAll(path, 0777)) } else { @@ -93,7 +93,7 @@ func checkMkdirAll(t *testing.T, path string, valid bool, errormsgs... string) { } } -func checkMkdirAllSubdirs(t *testing.T, path string, valid bool, errormsgs... string) { +func checkMkdirAllSubdirs(t *testing.T, path string, valid bool, errormsgs ...string) { checkMkdirAll(t, path, valid, errormsgs...) checkMkdirAll(t, path+`\`, valid, errormsgs...) checkMkdirAll(t, path+`\parent`, valid, errormsgs...) diff --git a/lib/oauthutil/oauthutil.go b/lib/oauthutil/oauthutil.go index 5f3e62f23..80fe41b82 100644 --- a/lib/oauthutil/oauthutil.go +++ b/lib/oauthutil/oauthutil.go @@ -10,6 +10,7 @@ import ( "net" "net/http" "net/url" + "os" "strings" "sync" "time" @@ -24,6 +25,11 @@ import ( "golang.org/x/oauth2" ) +var ( + // templateString is the template used in the authorization webserver + templateString string +) + const ( // TitleBarRedirectURL is the OAuth2 redirect URL to use when the authorization // code should be returned in the title bar of the browser, with the page text @@ -49,8 +55,8 @@ const ( // redirects to the local webserver RedirectPublicSecureURL = "https://oauth.rclone.org/" - // AuthResponseTemplate is a template to handle the redirect URL for oauth requests - AuthResponseTemplate = ` + // DefaultAuthResponseTemplate is the default template used in the authorization webserver + DefaultAuthResponseTemplate = ` @@ -587,6 +593,23 @@ version recommended): } return fs.ConfigGoto(newState("*oauth-done")) case "*oauth-do": + // Make sure we can read the HTML template file if it was specified. + configTemplateFile, _ := m.Get("config_template_file") + configTemplateString, _ := m.Get("config_template") + + if configTemplateFile != "" { + dat, err := os.ReadFile(configTemplateFile) + + if err != nil { + return nil, fmt.Errorf("failed to read template file: %w", err) + } + + templateString = string(dat) + } else if configTemplateString != "" { + templateString = configTemplateString + } else { + templateString = DefaultAuthResponseTemplate + } code := in.Result opt, err := getOAuth() if err != nil { @@ -755,7 +778,7 @@ func (s *authServer) handleAuth(w http.ResponseWriter, req *http.Request) { reply := func(status int, res *AuthResult) { w.WriteHeader(status) w.Header().Set("Content-Type", "text/html") - var t = template.Must(template.New("authResponse").Parse(AuthResponseTemplate)) + var t = template.Must(template.New("authResponse").Parse(templateString)) if err := t.Execute(w, res); err != nil { fs.Debugf(nil, "Could not execute template for web response.") }