authorize: add support for custom templates

This adds support for providing custom Go templates for use in the
`rclone authorize` command.

Fixes #6741
This commit is contained in:
Hunter Wittenborn 2023-02-24 09:08:38 -06:00 committed by GitHub
parent 745c0af571
commit 56b582cdb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 54 additions and 10 deletions

View File

@ -12,12 +12,14 @@ import (
var ( var (
noAutoBrowser bool noAutoBrowser bool
template string
) )
func init() { func init() {
cmd.Root.AddCommand(commandDefinition) cmd.Root.AddCommand(commandDefinition)
cmdFlags := commandDefinition.Flags() cmdFlags := commandDefinition.Flags()
flags.BoolVarP(cmdFlags, &noAutoBrowser, "auth-no-open-browser", "", false, "Do not automatically open auth link in default browser") 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{ 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 from a machine with a browser - use as instructed by
rclone config. 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.`, 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{ Annotations: map[string]string{
"versionIntroduced": "v1.27", "versionIntroduced": "v1.27",
}, },
RunE: func(command *cobra.Command, args []string) error { RunE: func(command *cobra.Command, args []string) error {
cmd.CheckArgs(1, 3, command, args) cmd.CheckArgs(1, 3, command, args)
return config.Authorize(context.Background(), args, noAutoBrowser) return config.Authorize(context.Background(), args, noAutoBrowser, template)
}, },
} }

View File

@ -1,5 +1,6 @@
// Code generated by vfsgen; DO NOT EDIT. // Code generated by vfsgen; DO NOT EDIT.
//go:build !dev
// +build !dev // +build !dev
package data package data

View File

@ -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 from a machine with a browser - use as instructed by
rclone config. 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. 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] rclone authorize [flags]
``` ```
@ -29,6 +33,7 @@ rclone authorize [flags]
``` ```
--auth-no-open-browser Do not automatically open auth link in default browser --auth-no-open-browser Do not automatically open auth link in default browser
-h, --help help for authorize -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. See the [global flags page](/flags/) for global options not listed here.

View File

@ -15,7 +15,7 @@ import (
// rclone authorize "fs name" // rclone authorize "fs name"
// rclone authorize "fs name" "base64 encoded JSON blob" // rclone authorize "fs name" "base64 encoded JSON blob"
// rclone authorize "fs name" "client id" "client secret" // 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 = suppressConfirm(ctx)
ctx = fs.ConfigOAuthOnly(ctx) ctx = fs.ConfigOAuthOnly(ctx)
switch len(args) { switch len(args) {
@ -41,6 +41,11 @@ func Authorize(ctx context.Context, args []string, noAutoBrowser bool) error {
inM[ConfigAuthNoBrowser] = "true" inM[ConfigAuthNoBrowser] = "true"
} }
// Indicate if we specified a custom template via a file
if templateFile != "" {
inM[ConfigTemplateFile] = templateFile
}
// Add extra parameters if supplied // Add extra parameters if supplied
if len(args) == 2 { if len(args) == 2 {
err := inM.Decode(args[1]) err := inM.Decode(args[1])

View File

@ -58,6 +58,12 @@ const (
// ConfigAuthNoBrowser indicates that we do not want to open browser // ConfigAuthNoBrowser indicates that we do not want to open browser
ConfigAuthNoBrowser = "config_auth_no_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 // Storage defines an interface for loading and saving config to

View File

@ -77,7 +77,7 @@ func unusedDrive(t *testing.T) string {
return string(letter) + ":" 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 { if valid {
assert.NoError(t, MkdirAll(path, 0777)) assert.NoError(t, MkdirAll(path, 0777))
} else { } 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+`\`, valid, errormsgs...) checkMkdirAll(t, path+`\`, valid, errormsgs...)
checkMkdirAll(t, path+`\parent`, valid, errormsgs...) checkMkdirAll(t, path+`\parent`, valid, errormsgs...)

View File

@ -10,6 +10,7 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -24,6 +25,11 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
var (
// templateString is the template used in the authorization webserver
templateString string
)
const ( const (
// TitleBarRedirectURL is the OAuth2 redirect URL to use when the authorization // 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 // 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 // redirects to the local webserver
RedirectPublicSecureURL = "https://oauth.rclone.org/" RedirectPublicSecureURL = "https://oauth.rclone.org/"
// AuthResponseTemplate is a template to handle the redirect URL for oauth requests // DefaultAuthResponseTemplate is the default template used in the authorization webserver
AuthResponseTemplate = `<!DOCTYPE html> DefaultAuthResponseTemplate = `<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@ -587,6 +593,23 @@ version recommended):
} }
return fs.ConfigGoto(newState("*oauth-done")) return fs.ConfigGoto(newState("*oauth-done"))
case "*oauth-do": 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 code := in.Result
opt, err := getOAuth() opt, err := getOAuth()
if err != nil { if err != nil {
@ -755,7 +778,7 @@ func (s *authServer) handleAuth(w http.ResponseWriter, req *http.Request) {
reply := func(status int, res *AuthResult) { reply := func(status int, res *AuthResult) {
w.WriteHeader(status) w.WriteHeader(status)
w.Header().Set("Content-Type", "text/html") 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 { if err := t.Execute(w, res); err != nil {
fs.Debugf(nil, "Could not execute template for web response.") fs.Debugf(nil, "Could not execute template for web response.")
} }