From 1b2af90e5a95f69ff6f73c32f17b5f1318fee75f Mon Sep 17 00:00:00 2001 From: James Elliott Date: Wed, 2 Mar 2022 18:50:36 +1100 Subject: [PATCH] feat(commands): totp qr code in png format (#2673) This allows exporting the TOTP QR code for easy registration when using `authelia storage totp generate` or `authelia storage totp export`. --- internal/commands/const.go | 1 + internal/commands/storage.go | 2 + internal/commands/storage_run.go | 218 ++++++++++++++------- internal/models/totp_configuration.go | 19 ++ internal/models/totp_configuration_test.go | 37 ++++ internal/suites/suite_cli.go | 2 + internal/suites/suite_cli_test.go | 188 ++++++++++++------ 7 files changed, 333 insertions(+), 134 deletions(-) diff --git a/internal/commands/const.go b/internal/commands/const.go index 40edb6a8f..eb1f1a6cb 100644 --- a/internal/commands/const.go +++ b/internal/commands/const.go @@ -105,6 +105,7 @@ const ( const ( storageExportFormatCSV = "csv" storageExportFormatURI = "uri" + storageExportFormatPNG = "png" ) var ( diff --git a/internal/commands/storage.go b/internal/commands/storage.go index 02c6e22fa..fc6e6580b 100644 --- a/internal/commands/storage.go +++ b/internal/commands/storage.go @@ -112,6 +112,7 @@ func newStorageTOTPGenerateCmd() (cmd *cobra.Command) { cmd.Flags().String("algorithm", "SHA1", "set the TOTP algorithm") cmd.Flags().String("issuer", "Authelia", "set the TOTP issuer") cmd.Flags().BoolP("force", "f", false, "forces the TOTP configuration to be generated regardless if it exists or not") + cmd.Flags().StringP("path", "p", "", "path to a file to create a PNG file with the QR code (optional)") return cmd } @@ -135,6 +136,7 @@ func newStorageTOTPExportCmd() (cmd *cobra.Command) { } cmd.Flags().String("format", storageExportFormatURI, "sets the output format") + cmd.Flags().String("dir", "", "used with the png output format to specify which new directory to save the files in") return cmd } diff --git a/internal/commands/storage_run.go b/internal/commands/storage_run.go index 953f9b01c..2f0b06a50 100644 --- a/internal/commands/storage_run.go +++ b/internal/commands/storage_run.go @@ -4,7 +4,10 @@ import ( "context" "errors" "fmt" + "image" + "image/png" "os" + "path/filepath" "strings" "github.com/spf13/cobra" @@ -15,11 +18,13 @@ import ( "github.com/authelia/authelia/v4/internal/models" "github.com/authelia/authelia/v4/internal/storage" "github.com/authelia/authelia/v4/internal/totp" + "github.com/authelia/authelia/v4/internal/utils" ) func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) { - configs, err := cmd.Flags().GetStringSlice("config") - if err != nil { + var configs []string + + if configs, err = cmd.Flags().GetStringSlice("config"); err != nil { return err } @@ -72,8 +77,7 @@ func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) { config = &schema.Configuration{} - _, err = configuration.LoadAdvanced(val, "", &config, sources...) - if err != nil { + if _, err = configuration.LoadAdvanced(val, "", &config, sources...); err != nil { return err } @@ -117,7 +121,9 @@ func storagePersistentPreRunE(cmd *cobra.Command, _ []string) (err error) { func storageSchemaEncryptionCheckRunE(cmd *cobra.Command, args []string) (err error) { var ( provider storage.Provider - ctx = context.Background() + verbose bool + + ctx = context.Background() ) provider = getStorageProvider() @@ -126,8 +132,7 @@ func storageSchemaEncryptionCheckRunE(cmd *cobra.Command, args []string) (err er _ = provider.Close() }() - verbose, err := cmd.Flags().GetBool("verbose") - if err != nil { + if verbose, err = cmd.Flags().GetBool("verbose"); err != nil { return err } @@ -150,7 +155,10 @@ func storageSchemaEncryptionCheckRunE(cmd *cobra.Command, args []string) (err er func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (err error) { var ( provider storage.Provider - ctx = context.Background() + key string + version int + + ctx = context.Background() ) provider = getStorageProvider() @@ -163,8 +171,7 @@ func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (er return err } - version, err := provider.SchemaVersion(ctx) - if err != nil { + if version, err = provider.SchemaVersion(ctx); err != nil { return err } @@ -172,17 +179,15 @@ func storageSchemaEncryptionChangeKeyRunE(cmd *cobra.Command, args []string) (er return errors.New("schema version must be at least version 1 to change the encryption key") } - key, err := cmd.Flags().GetString("new-encryption-key") - if err != nil { + key, err = cmd.Flags().GetString("new-encryption-key") + + switch { + case err != nil: return err - } - - if key == "" { + case key == "": return errors.New("you must set the --new-encryption-key flag") - } - - if len(key) < 20 { - return errors.New("the encryption key must be at least 20 characters") + case len(key) < 20: + return errors.New("the new encryption key must be at least 20 characters") } if err = provider.SchemaEncryptionChangeKey(ctx, key); err != nil { @@ -200,6 +205,9 @@ func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) { ctx = context.Background() c *models.TOTPConfiguration force bool + filename string + file *os.File + img image.Image ) provider = getStorageProvider() @@ -208,10 +216,15 @@ func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) { _ = provider.Close() }() - force, err = cmd.Flags().GetBool("force") + if force, err = cmd.Flags().GetBool("force"); err != nil { + return err + } - _, err = provider.LoadTOTPConfiguration(ctx, args[0]) - if err == nil && !force { + if filename, err = cmd.Flags().GetString("path"); err != nil { + return err + } + + if _, err = provider.LoadTOTPConfiguration(ctx, args[0]); err == nil && !force { return fmt.Errorf("%s already has a TOTP configuration, use --force to overwrite", args[0]) } @@ -225,12 +238,35 @@ func storageTOTPGenerateRunE(cmd *cobra.Command, args []string) (err error) { return err } - err = provider.SaveTOTPConfiguration(ctx, *c) - if err != nil { + extraInfo := "" + + if filename != "" { + if _, err = os.Stat(filename); !os.IsNotExist(err) { + return errors.New("image output filepath already exists") + } + + if file, err = os.Create(filename); err != nil { + return err + } + + defer file.Close() + + if img, err = c.Image(256, 256); err != nil { + return err + } + + if err = png.Encode(file, img); err != nil { + return err + } + + extraInfo = fmt.Sprintf(" and saved it as a PNG image at the path '%s'", filename) + } + + if err = provider.SaveTOTPConfiguration(ctx, *c); err != nil { return err } - fmt.Printf("Generated TOTP configuration for user '%s': %s", args[0], c.URI()) + fmt.Printf("Generated TOTP configuration for user '%s' with URI '%s'%s\n", args[0], c.URI(), extraInfo) return nil } @@ -249,13 +285,11 @@ func storageTOTPDeleteRunE(cmd *cobra.Command, args []string) (err error) { _ = provider.Close() }() - _, err = provider.LoadTOTPConfiguration(ctx, user) - if err != nil { + if _, err = provider.LoadTOTPConfiguration(ctx, user); err != nil { return fmt.Errorf("can't delete configuration for user '%s': %+v", user, err) } - err = provider.DeleteTOTPConfiguration(ctx, user) - if err != nil { + if err = provider.DeleteTOTPConfiguration(ctx, user); err != nil { return fmt.Errorf("can't delete configuration for user '%s': %+v", user, err) } @@ -266,8 +300,12 @@ func storageTOTPDeleteRunE(cmd *cobra.Command, args []string) (err error) { func storageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) { var ( - provider storage.Provider - ctx = context.Background() + provider storage.Provider + format, dir string + configurations []models.TOTPConfiguration + img image.Image + + ctx = context.Background() ) provider = getStorageProvider() @@ -280,25 +318,14 @@ func storageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) { return err } - format, err := cmd.Flags().GetString("format") - if err != nil { + if format, dir, err = storageTOTPExportGetConfigFromFlags(cmd); err != nil { return err } - switch format { - case storageExportFormatCSV, storageExportFormatURI: - break - default: - return errors.New("format must be csv or uri") - } - limit := 10 - var configurations []models.TOTPConfiguration - for page := 0; true; page++ { - configurations, err = provider.LoadTOTPConfigurations(ctx, limit, page) - if err != nil { + if configurations, err = provider.LoadTOTPConfigurations(ctx, limit, page); err != nil { return err } @@ -312,6 +339,17 @@ func storageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) { fmt.Printf("%s,%s,%s,%d,%d,%s\n", c.Issuer, c.Username, c.Algorithm, c.Digits, c.Period, string(c.Secret)) case storageExportFormatURI: fmt.Println(c.URI()) + case storageExportFormatPNG: + file, _ := os.Create(filepath.Join(dir, fmt.Sprintf("%s.png", c.Username))) + defer file.Close() + + if img, err = c.Image(256, 256); err != nil { + return err + } + + if err = png.Encode(file, img); err != nil { + return err + } } } @@ -320,13 +358,51 @@ func storageTOTPExportRunE(cmd *cobra.Command, args []string) (err error) { } } + if format == storageExportFormatPNG { + fmt.Printf("Exported TOTP QR codes in PNG format in the '%s' directory\n", dir) + } + return nil } +func storageTOTPExportGetConfigFromFlags(cmd *cobra.Command) (format, dir string, err error) { + if format, err = cmd.Flags().GetString("format"); err != nil { + return "", "", err + } + + if dir, err = cmd.Flags().GetString("dir"); err != nil { + return "", "", err + } + + switch format { + case storageExportFormatCSV, storageExportFormatURI: + break + case storageExportFormatPNG: + if dir == "" { + dir = utils.RandomString(8, utils.AlphaNumericCharacters, false) + } + + if _, err = os.Stat(dir); !os.IsNotExist(err) { + return "", "", errors.New("output directory must not exist") + } + + if err = os.MkdirAll(dir, 0700); err != nil { + return "", "", err + } + default: + return "", "", errors.New("format must be csv, uri, or png") + } + + return format, dir, nil +} + func storageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) { var ( - provider storage.Provider - ctx = context.Background() + provider storage.Provider + version int + migrations []models.Migration + + ctx = context.Background() ) provider = getStorageProvider() @@ -338,8 +414,7 @@ func storageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) { _ = provider.Close() }() - version, err := provider.SchemaVersion(ctx) - if err != nil { + if version, err = provider.SchemaVersion(ctx); err != nil { return err } @@ -348,8 +423,7 @@ func storageMigrateHistoryRunE(_ *cobra.Command, _ []string) (err error) { return } - migrations, err := provider.SchemaMigrationHistory(ctx) - if err != nil { + if migrations, err = provider.SchemaMigrationHistory(ctx); err != nil { return err } @@ -411,7 +485,10 @@ func newStorageMigrationRunE(up bool) func(cmd *cobra.Command, args []string) (e return func(cmd *cobra.Command, args []string) (err error) { var ( provider storage.Provider - ctx = context.Background() + target int + pre1 bool + + ctx = context.Background() ) provider = getStorageProvider() @@ -420,8 +497,7 @@ func newStorageMigrationRunE(up bool) func(cmd *cobra.Command, args []string) (e _ = provider.Close() }() - target, err := cmd.Flags().GetInt("target") - if err != nil { + if target, err = cmd.Flags().GetInt("target"); err != nil { return err } @@ -434,8 +510,7 @@ func newStorageMigrationRunE(up bool) func(cmd *cobra.Command, args []string) (e return provider.SchemaMigrate(ctx, true, storage.SchemaLatest) } default: - pre1, err := cmd.Flags().GetBool("pre1") - if err != nil { + if pre1, err = cmd.Flags().GetBool("pre1"); err != nil { return err } @@ -458,8 +533,9 @@ func newStorageMigrationRunE(up bool) func(cmd *cobra.Command, args []string) (e } func storageMigrateDownConfirmDestroy(cmd *cobra.Command) (err error) { - destroy, err := cmd.Flags().GetBool("destroy-data") - if err != nil { + var destroy bool + + if destroy, err = cmd.Flags().GetBool("destroy-data"); err != nil { return err } @@ -480,10 +556,13 @@ func storageMigrateDownConfirmDestroy(cmd *cobra.Command) (err error) { func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) { var ( - provider storage.Provider - ctx = context.Background() - upgradeStr string - tablesStr string + upgradeStr, tablesStr string + + provider storage.Provider + tables []string + version, latest int + + ctx = context.Background() ) provider = getStorageProvider() @@ -492,13 +571,11 @@ func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) { _ = provider.Close() }() - version, err := provider.SchemaVersion(ctx) - if err != nil && err.Error() != "unknown schema state" { + if version, err = provider.SchemaVersion(ctx); err != nil && err.Error() != "unknown schema state" { return err } - tables, err := provider.SchemaTables(ctx) - if err != nil { + if tables, err = provider.SchemaTables(ctx); err != nil { return err } @@ -508,8 +585,7 @@ func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) { tablesStr = strings.Join(tables, ", ") } - latest, err := provider.SchemaLatestVersion() - if err != nil { + if latest, err = provider.SchemaLatestVersion(); err != nil { return err } @@ -537,13 +613,13 @@ func storageSchemaInfoRunE(_ *cobra.Command, _ []string) (err error) { } func checkStorageSchemaUpToDate(ctx context.Context, provider storage.Provider) (err error) { - version, err := provider.SchemaVersion(ctx) - if err != nil { + var version, latest int + + if version, err = provider.SchemaVersion(ctx); err != nil { return err } - latest, err := provider.SchemaLatestVersion() - if err != nil { + if latest, err = provider.SchemaLatestVersion(); err != nil { return err } diff --git a/internal/models/totp_configuration.go b/internal/models/totp_configuration.go index 758a47c7c..9e8105e26 100644 --- a/internal/models/totp_configuration.go +++ b/internal/models/totp_configuration.go @@ -1,8 +1,11 @@ package models import ( + "image" "net/url" "strconv" + + "github.com/pquerna/otp" ) // TOTPConfiguration represents a users TOTP configuration row in the database. @@ -34,3 +37,19 @@ func (c TOTPConfiguration) URI() (uri string) { return u.String() } + +// Key returns the *otp.Key using TOTPConfiguration.URI with otp.NewKeyFromURL. +func (c TOTPConfiguration) Key() (key *otp.Key, err error) { + return otp.NewKeyFromURL(c.URI()) +} + +// Image returns the image.Image of the TOTPConfiguration using the Image func from the return of TOTPConfiguration.Key. +func (c TOTPConfiguration) Image(width, height int) (img image.Image, err error) { + var key *otp.Key + + if key, err = c.Key(); err != nil { + return nil, err + } + + return key.Image(width, height) +} diff --git a/internal/models/totp_configuration_test.go b/internal/models/totp_configuration_test.go index c23901997..1e8aed407 100644 --- a/internal/models/totp_configuration_test.go +++ b/internal/models/totp_configuration_test.go @@ -38,3 +38,40 @@ func TestShouldOnlyMarshalPeriodAndDigitsAndAbsolutelyNeverSecret(t *testing.T) require.NotContains(t, string(data), "secret") require.NotContains(t, string(data), "ABC123") } + +func TestShouldReturnErrWhenImageTooSmall(t *testing.T) { + object := TOTPConfiguration{ + ID: 1, + Username: "john", + Issuer: "Authelia", + Algorithm: "SHA1", + Digits: 6, + Period: 30, + Secret: []byte("ABC123"), + } + + img, err := object.Image(10, 10) + + assert.EqualError(t, err, "can not scale barcode to an image smaller than 41x41") + assert.Nil(t, img) +} + +func TestShouldReturnImage(t *testing.T) { + object := TOTPConfiguration{ + ID: 1, + Username: "john", + Issuer: "Authelia", + Algorithm: "SHA1", + Digits: 6, + Period: 30, + Secret: []byte("ABC123"), + } + + img, err := object.Image(41, 41) + + assert.NoError(t, err) + require.NotNil(t, img) + + assert.Equal(t, 41, img.Bounds().Dx()) + assert.Equal(t, 41, img.Bounds().Dy()) +} diff --git a/internal/suites/suite_cli.go b/internal/suites/suite_cli.go index 44210af6a..64a544702 100644 --- a/internal/suites/suite_cli.go +++ b/internal/suites/suite_cli.go @@ -38,6 +38,8 @@ func init() { err := dockerEnvironment.Down() _ = os.Remove("/tmp/db.sqlite3") _ = os.Remove("/tmp/db.sqlite") + _ = os.RemoveAll("/tmp/qr/") + _ = os.Remove("/tmp/qr.png") return err } diff --git a/internal/suites/suite_cli_test.go b/internal/suites/suite_cli_test.go index 83768345f..90ac14c02 100644 --- a/internal/suites/suite_cli_test.go +++ b/internal/suites/suite_cli_test.go @@ -66,13 +66,13 @@ func (s *CLISuite) TestShouldPrintVersion() { } func (s *CLISuite) TestShouldValidateConfig() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "--config", "/config/configuration.yml"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "--config=/config/configuration.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Configuration parsed and loaded successfully without errors.") } func (s *CLISuite) TestShouldFailValidateConfig() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "--config", "/config/invalid.yml"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "validate-config", "--config=/config/invalid.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "failed to load configuration from yaml file(/config/invalid.yml) source: open /config/invalid.yml: no such file or directory") } @@ -90,75 +90,75 @@ func (s *CLISuite) TestShouldHashPasswordSHA512() { } func (s *CLISuite) TestShouldGenerateCertificateRSA() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/"}) s.Assert().NoError(err) s.Assert().Contains(output, "Certificate Public Key written to /tmp/cert.pem") s.Assert().Contains(output, "Certificate Private Key written to /tmp/key.pem") } func (s *CLISuite) TestShouldGenerateCertificateRSAWithIPAddress() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "127.0.0.1", "--dir", "/tmp/"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=127.0.0.1", "--dir=/tmp/"}) s.Assert().NoError(err) s.Assert().Contains(output, "Certificate Public Key written to /tmp/cert.pem") s.Assert().Contains(output, "Certificate Private Key written to /tmp/key.pem") } func (s *CLISuite) TestShouldGenerateCertificateRSAWithStartDate() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/", "--start-date", "'Jan 1 15:04:05 2011'"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/", "--start-date='Jan 1 15:04:05 2011'"}) s.Assert().NoError(err) s.Assert().Contains(output, "Certificate Public Key written to /tmp/cert.pem") s.Assert().Contains(output, "Certificate Private Key written to /tmp/key.pem") } func (s *CLISuite) TestShouldFailGenerateCertificateRSAWithStartDate() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/", "--start-date", "Jan"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/", "--start-date=Jan"}) s.Assert().NotNil(err) s.Assert().Contains(output, "Failed to parse start date: parsing time \"Jan\" as \"Jan 2 15:04:05 2006\": cannot parse \"\" as \"2\"") } func (s *CLISuite) TestShouldGenerateCertificateCA() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/", "--ca"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/", "--ca"}) s.Assert().NoError(err) s.Assert().Contains(output, "Certificate Public Key written to /tmp/cert.pem") s.Assert().Contains(output, "Certificate Private Key written to /tmp/key.pem") } func (s *CLISuite) TestShouldGenerateCertificateEd25519() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/", "--ed25519"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/", "--ed25519"}) s.Assert().NoError(err) s.Assert().Contains(output, "Certificate Public Key written to /tmp/cert.pem") s.Assert().Contains(output, "Certificate Private Key written to /tmp/key.pem") } func (s *CLISuite) TestShouldFailGenerateCertificateECDSA() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/", "--ecdsa-curve", "invalid"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/", "--ecdsa-curve=invalid"}) s.Assert().NotNil(err) s.Assert().Contains(output, "Failed to generate private key: unrecognized elliptic curve: \"invalid\"") } func (s *CLISuite) TestShouldGenerateCertificateECDSAP224() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/", "--ecdsa-curve", "P224"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/", "--ecdsa-curve=P224"}) s.Assert().NoError(err) s.Assert().Contains(output, "Certificate Public Key written to /tmp/cert.pem") s.Assert().Contains(output, "Certificate Private Key written to /tmp/key.pem") } func (s *CLISuite) TestShouldGenerateCertificateECDSAP256() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/", "--ecdsa-curve", "P256"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/", "--ecdsa-curve=P256"}) s.Assert().NoError(err) s.Assert().Contains(output, "Certificate Public Key written to /tmp/cert.pem") s.Assert().Contains(output, "Certificate Private Key written to /tmp/key.pem") } func (s *CLISuite) TestShouldGenerateCertificateECDSAP384() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/", "--ecdsa-curve", "P384"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/", "--ecdsa-curve=P384"}) s.Assert().NoError(err) s.Assert().Contains(output, "Certificate Public Key written to /tmp/cert.pem") s.Assert().Contains(output, "Certificate Private Key written to /tmp/key.pem") } func (s *CLISuite) TestShouldGenerateCertificateECDSAP521() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host", "*.example.com", "--dir", "/tmp/", "--ecdsa-curve", "P521"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "certificates", "generate", "--host=*.example.com", "--dir=/tmp/", "--ecdsa-curve=P521"}) s.Assert().NoError(err) s.Assert().Contains(output, "Certificate Public Key written to /tmp/cert.pem") s.Assert().Contains(output, "Certificate Private Key written to /tmp/key.pem") @@ -179,7 +179,7 @@ func (s *CLISuite) TestStorageShouldShowErrWithoutConfig() { func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() { _ = os.Remove("/tmp/db.sqlite3") - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info", "--config", "/config/configuration.storage.yml"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) pattern := regexp.MustCompile(`^Schema Version: N/A\nSchema Upgrade Available: yes - version \d+\nSchema Tables: N/A\nSchema Encryption Key: unsupported \(schema version\)`) @@ -188,45 +188,45 @@ func (s *CLISuite) TestStorage00ShouldShowCorrectPreInitInformation() { patternOutdated := regexp.MustCompile(`Error: schema is version \d+ which is outdated please migrate to version \d+ in order to use this command or use an older binary`) - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "export", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "export", "--config=/config/configuration.storage.yml"}) s.Assert().EqualError(err, "exit status 1") s.Assert().Regexp(patternOutdated, output) - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "change-key", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "change-key", "--config=/config/configuration.storage.yml"}) s.Assert().EqualError(err, "exit status 1") s.Assert().Regexp(patternOutdated, output) - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Could not check encryption key for validity. The schema version doesn't support encryption.") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "down", "--target", "0", "--destroy-data", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "down", "--target=0", "--destroy-data", "--config=/config/configuration.storage.yml"}) s.Assert().EqualError(err, "exit status 1") s.Assert().Contains(output, "Error: schema migration target version 0 is the same current version 0") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "up", "--target", "2147483640", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "up", "--target=2147483640", "--config=/config/configuration.storage.yml"}) s.Assert().EqualError(err, "exit status 1") s.Assert().Contains(output, "Error: schema up migration target version 2147483640 is greater then the latest version ") s.Assert().Contains(output, " which indicates it doesn't exist") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "--config", "/config/configuration.storage.yml", "migrate", "history"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "history", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "No migration history is available for schemas that not version 1 or above.\n") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "--config", "/config/configuration.storage.yml", "migrate", "list-up"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "list-up", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Storage Schema Migration List (Up)\n\nVersion\t\tDescription\n1\t\tInitial Schema\n") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "--config", "/config/configuration.storage.yml", "migrate", "list-down"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "list-down", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Storage Schema Migration List (Down)\n\nNo Migrations Available\n") } func (s *CLISuite) TestStorage01ShouldMigrateUp() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "--config", "/config/configuration.storage.yml", "migrate", "up"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "up", "--config=/config/configuration.storage.yml"}) s.Require().NoError(err) pattern0 := regexp.MustCompile(`"Storage schema migration from \d+ to \d+ is being attempted"`) @@ -235,23 +235,23 @@ func (s *CLISuite) TestStorage01ShouldMigrateUp() { s.Regexp(pattern0, output) s.Regexp(pattern1, output) - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "--config", "/config/configuration.storage.yml", "migrate", "up"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "up", "--config=/config/configuration.storage.yml"}) s.Assert().EqualError(err, "exit status 1") s.Assert().Contains(output, "Error: schema already up to date\n") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "--config", "/config/configuration.storage.yml", "migrate", "history"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "history", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Migration History:\n\nID\tDate\t\t\t\tBefore\tAfter\tAuthelia Version\n") s.Assert().Contains(output, "0\t1") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "--config", "/config/configuration.storage.yml", "migrate", "list-up"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "list-up", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Storage Schema Migration List (Up)\n\nNo Migrations Available") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "--config", "/config/configuration.storage.yml", "migrate", "list-down"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "list-down", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Storage Schema Migration List (Down)\n\nVersion\t\tDescription\n") @@ -259,7 +259,7 @@ func (s *CLISuite) TestStorage01ShouldMigrateUp() { } func (s *CLISuite) TestStorage02ShouldShowSchemaInfo() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info", "--config", "/config/configuration.storage.yml"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) pattern := regexp.MustCompile(`^Schema Version: \d+\nSchema Upgrade Available: no\nSchema Tables: authentication_logs, identity_verification, totp_configurations, u2f_devices, duo_devices, user_preferences, migrations, encryption\nSchema Encryption Key: valid`) @@ -284,95 +284,157 @@ func (s *CLISuite) TestStorage03ShouldExportTOTP() { expectedLinesCSV = append(expectedLinesCSV, "issuer,username,algorithm,digits,period,secret") - configs := []*models.TOTPConfiguration{ + testCases := []struct { + config models.TOTPConfiguration + png bool + }{ { - Username: "john", - Period: 30, - Digits: 6, - Algorithm: "SHA1", + config: models.TOTPConfiguration{ + Username: "john", + Period: 30, + Digits: 6, + Algorithm: "SHA1", + }, }, { - Username: "mary", - Period: 45, - Digits: 6, - Algorithm: "SHA1", + config: models.TOTPConfiguration{ + Username: "mary", + Period: 45, + Digits: 6, + Algorithm: "SHA1", + }, }, { - Username: "fred", - Period: 30, - Digits: 8, - Algorithm: "SHA1", + config: models.TOTPConfiguration{ + Username: "fred", + Period: 30, + Digits: 8, + Algorithm: "SHA1", + }, }, { - Username: "jone", - Period: 30, - Digits: 6, - Algorithm: "SHA512", + config: models.TOTPConfiguration{ + Username: "jone", + Period: 30, + Digits: 6, + Algorithm: "SHA512", + }, + png: true, }, } - for _, config := range configs { - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "generate", config.Username, "--period", strconv.Itoa(int(config.Period)), "--algorithm", config.Algorithm, "--digits", strconv.Itoa(int(config.Digits)), "--config", "/config/configuration.storage.yml"}) + var ( + config *models.TOTPConfiguration + fileInfo os.FileInfo + ) + + for _, testCase := range testCases { + if testCase.png { + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "generate", testCase.config.Username, "--period", strconv.Itoa(int(testCase.config.Period)), "--algorithm", testCase.config.Algorithm, "--digits", strconv.Itoa(int(testCase.config.Digits)), "--path=/tmp/qr.png", "--config=/config/configuration.storage.yml"}) + s.Assert().NoError(err) + s.Assert().Contains(output, " and saved it as a PNG image at the path '/tmp/qr.png'") + + fileInfo, err = os.Stat("/tmp/qr.png") + s.Assert().NoError(err) + s.Require().NotNil(fileInfo) + s.Assert().False(fileInfo.IsDir()) + s.Assert().Greater(fileInfo.Size(), int64(1000)) + } else { + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "generate", testCase.config.Username, "--period", strconv.Itoa(int(testCase.config.Period)), "--algorithm", testCase.config.Algorithm, "--digits", strconv.Itoa(int(testCase.config.Digits)), "--config=/config/configuration.storage.yml"}) + s.Assert().NoError(err) + } + + config, err = storageProvider.LoadTOTPConfiguration(ctx, testCase.config.Username) s.Assert().NoError(err) - config, err = storageProvider.LoadTOTPConfiguration(ctx, config.Username) - s.Assert().NoError(err) s.Assert().Contains(output, config.URI()) expectedLinesCSV = append(expectedLinesCSV, fmt.Sprintf("%s,%s,%s,%d,%d,%s", "Authelia", config.Username, config.Algorithm, config.Digits, config.Period, string(config.Secret))) expectedLines = append(expectedLines, config.URI()) } - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "export", "--format", "uri", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "export", "--format=uri", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) for _, expectedLine := range expectedLines { s.Assert().Contains(output, expectedLine) } - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "export", "--format", "csv", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "export", "--format=csv", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) for _, expectedLine := range expectedLinesCSV { s.Assert().Contains(output, expectedLine) } + + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "export", "--format=wrong", "--config=/config/configuration.storage.yml"}) + s.Assert().EqualError(err, "exit status 1") + s.Assert().Contains(output, "Error: format must be csv, uri, or png") + + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "export", "--format=png", "--dir=/tmp/qr", "--config=/config/configuration.storage.yml"}) + s.Assert().NoError(err) + s.Assert().Contains(output, "Exported TOTP QR codes in PNG format in the '/tmp/qr' directory") + + for _, testCase := range testCases { + fileInfo, err = os.Stat(fmt.Sprintf("/tmp/qr/%s.png", testCase.config.Username)) + + s.Assert().NoError(err) + s.Require().NotNil(fileInfo) + + s.Assert().False(fileInfo.IsDir()) + s.Assert().Greater(fileInfo.Size(), int64(1000)) + } + + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "totp", "generate", "test", "--period=30", "--algorithm=SHA1", "--digits=6", "--path=/tmp/qr.png", "--config=/config/configuration.storage.yml"}) + s.Assert().EqualError(err, "exit status 1") + s.Assert().Contains(output, "Error: image output filepath already exists") } func (s *CLISuite) TestStorage04ShouldChangeEncryptionKey() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "change-key", "--new-encryption-key", "apple-apple-apple-apple", "--config", "/config/configuration.storage.yml"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "change-key", "--new-encryption-key=apple-apple-apple-apple", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Completed the encryption key change. Please adjust your configuration to use the new key.\n") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) pattern := regexp.MustCompile(`Schema Version: \d+\nSchema Upgrade Available: no\nSchema Tables: authentication_logs, identity_verification, totp_configurations, u2f_devices, duo_devices, user_preferences, migrations, encryption\nSchema Encryption Key: invalid`) s.Assert().Regexp(pattern, output) - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Encryption key validation: failed.\n\nError: the encryption key is not valid against the schema check value.\n") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--verbose", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--verbose", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Encryption key validation: failed.\n\nError: the encryption key is not valid against the schema check value, 4 of 4 total TOTP secrets were invalid.\n") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--encryption-key", "apple-apple-apple-apple", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--encryption-key=apple-apple-apple-apple", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Encryption key validation: success.\n") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--verbose", "--encryption-key", "apple-apple-apple-apple", "--config", "/config/configuration.storage.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--verbose", "--encryption-key=apple-apple-apple-apple", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) s.Assert().Contains(output, "Encryption key validation: success.\n") + + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "change-key", "--encryption-key=apple-apple-apple-apple", "--config=/config/configuration.storage.yml"}) + s.Assert().EqualError(err, "exit status 1") + + s.Assert().Contains(output, "Error: you must set the --new-encryption-key flag\n") + + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "change-key", "--encryption-key=apple-apple-apple-apple", "--new-encryption-key=abc", "--config=/config/configuration.storage.yml"}) + s.Assert().EqualError(err, "exit status 1") + + s.Assert().Contains(output, "Error: the new encryption key must be at least 20 characters\n") } func (s *CLISuite) TestStorage05ShouldMigrateDown() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "down", "--target", "0", "--destroy-data", "--config", "/config/configuration.storage.yml"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "migrate", "down", "--target=0", "--destroy-data", "--config=/config/configuration.storage.yml"}) s.Assert().NoError(err) pattern0 := regexp.MustCompile(`"Storage schema migration from \d+ to \d+ is being attempted"`) @@ -383,7 +445,7 @@ func (s *CLISuite) TestStorage05ShouldMigrateDown() { } func (s *CLISuite) TestACLPolicyCheckVerbose() { - output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://public.example.com", "--verbose", "--config", "/config/configuration.yml"}) + output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://public.example.com", "--verbose", "--config=/config/configuration.yml"}) s.Assert().NoError(err) // This is an example of `authelia access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://public.example.com --verbose`. @@ -400,7 +462,7 @@ func (s *CLISuite) TestACLPolicyCheckVerbose() { s.Contains(output, " 9\tmiss\thit\t\thit\thit\tmay\n") s.Contains(output, "The policy 'bypass' from rule #1 will be applied to this request.") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://admin.example.com", "--method=HEAD", "--username=tom", "--groups=basic,test", "--ip=192.168.2.3", "--verbose", "--config", "/config/configuration.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://admin.example.com", "--method=HEAD", "--username=tom", "--groups=basic,test", "--ip=192.168.2.3", "--verbose", "--config=/config/configuration.yml"}) s.Assert().NoError(err) // This is an example of `authelia access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://admin.example.com --method=HEAD --username=tom --groups=basic,test --ip=192.168.2.3 --verbose`. @@ -418,7 +480,7 @@ func (s *CLISuite) TestACLPolicyCheckVerbose() { s.Contains(output, " 9\tmiss\thit\t\thit\thit\tmiss\n") s.Contains(output, "The policy 'two_factor' from rule #2 will be applied to this request.") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://resources.example.com/resources/test", "--method=POST", "--username=john", "--groups=admin,test", "--ip=192.168.1.3", "--verbose", "--config", "/config/configuration.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://resources.example.com/resources/test", "--method=POST", "--username=john", "--groups=admin,test", "--ip=192.168.1.3", "--verbose", "--config=/config/configuration.yml"}) s.Assert().NoError(err) // This is an example of `authelia access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://resources.example.com/resources/test --method=POST --username=john --groups=admin,test --ip=192.168.1.3 --verbose`. @@ -435,7 +497,7 @@ func (s *CLISuite) TestACLPolicyCheckVerbose() { s.Contains(output, " 9\tmiss\thit\t\thit\thit\thit\n") s.Contains(output, "The policy 'one_factor' from rule #5 will be applied to this request.") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://user.example.com/resources/test", "--method=HEAD", "--username=john", "--groups=admin,test", "--ip=192.168.1.3", "--verbose", "--config", "/config/configuration.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://user.example.com/resources/test", "--method=HEAD", "--username=john", "--groups=admin,test", "--ip=192.168.1.3", "--verbose", "--config=/config/configuration.yml"}) s.Assert().NoError(err) // This is an example of `access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://user.example.com --method=HEAD --username=john --groups=admin,test --ip=192.168.1.3 --verbose`. @@ -452,7 +514,7 @@ func (s *CLISuite) TestACLPolicyCheckVerbose() { s.Contains(output, "* 9\thit\thit\t\thit\thit\thit\n") s.Contains(output, "The policy 'one_factor' from rule #9 will be applied to this request.") - output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://user.example.com", "--method=HEAD", "--ip=192.168.1.3", "--verbose", "--config", "/config/configuration.yml"}) + output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "access-control", "check-policy", "--url=https://user.example.com", "--method=HEAD", "--ip=192.168.1.3", "--verbose", "--config=/config/configuration.yml"}) s.Assert().NoError(err) // This is an example of `authelia access-control check-policy --config .\internal\suites\CLI\configuration.yml --url=https://user.example.com --method=HEAD --ip=192.168.1.3 --verbose`.