//go:build sync && metrics && mgmt && userprefs && search
// +build sync,metrics,mgmt,userprefs,search

package extensions_test

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"testing"
	"time"

	. "github.com/smartystreets/goconvey/convey"
	"gopkg.in/resty.v1"

	"zotregistry.dev/zot/pkg/api"
	"zotregistry.dev/zot/pkg/api/config"
	"zotregistry.dev/zot/pkg/api/constants"
	"zotregistry.dev/zot/pkg/extensions"
	extconf "zotregistry.dev/zot/pkg/extensions/config"
	syncconf "zotregistry.dev/zot/pkg/extensions/config/sync"
	authutils "zotregistry.dev/zot/pkg/test/auth"
	test "zotregistry.dev/zot/pkg/test/common"
)

const (
	ServerCert = "../../test/data/server.cert"
	ServerKey  = "../../test/data/server.key"
)

func TestEnableExtension(t *testing.T) {
	Convey("Verify log if sync disabled in config", t, func() {
		globalDir := t.TempDir()
		port := test.GetFreePort()
		conf := config.New()
		falseValue := false

		syncConfig := &syncconf.Config{
			Enable:     &falseValue,
			Registries: []syncconf.RegistryConfig{},
		}

		// conf.Extensions.Sync.Enable = &falseValue
		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Sync = syncConfig
		conf.HTTP.Port = port

		logFile, err := os.CreateTemp(globalDir, "zot-log*.txt")
		So(err, ShouldBeNil)
		conf.Log.Level = "info"
		conf.Log.Output = logFile.Name()

		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)
		ctlrManager := test.NewControllerManager(ctlr)

		defer ctlrManager.StopServer()

		ctlr.Config.Storage.RootDirectory = globalDir

		ctlrManager.StartAndWait(port)

		data, err := os.ReadFile(logFile.Name())
		So(err, ShouldBeNil)
		So(string(data), ShouldContainSubstring,
			"sync config not provided or disabled, so not enabling sync")
	})
}

func TestMetricsExtension(t *testing.T) {
	Convey("Verify Metrics enabled for storage subpaths", t, func() {
		globalDir := t.TempDir()
		conf := config.New()
		port := test.GetFreePort()
		conf.HTTP.Port = port

		logFile, err := os.CreateTemp(globalDir, "zot-log*.txt")
		So(err, ShouldBeNil)
		defaultValue := true

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Metrics = &extconf.MetricsConfig{
			BaseConfig: extconf.BaseConfig{Enable: &defaultValue},
			Prometheus: &extconf.PrometheusConfig{},
		}
		conf.Log.Level = "info"
		conf.Log.Output = logFile.Name()

		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)
		ctlrManager := test.NewControllerManager(ctlr)

		subPaths := make(map[string]config.StorageConfig)
		subPaths["/a"] = config.StorageConfig{
			Dedupe:        false,
			RootDirectory: t.TempDir(),
		}

		ctlr.Config.Storage.RootDirectory = globalDir
		ctlr.Config.Storage.SubPaths = subPaths

		ctlrManager.StartAndWait(port)

		data, _ := os.ReadFile(logFile.Name())

		So(string(data), ShouldContainSubstring,
			"prometheus instrumentation path not set, changing to '/metrics'.")
	})
}

func TestMgmtExtension(t *testing.T) {
	globalDir := t.TempDir()
	conf := config.New()
	port := test.GetFreePort()
	conf.HTTP.Port = port
	baseURL := test.GetBaseURL(port)

	logFile, err := os.CreateTemp(globalDir, "zot-log*.txt")
	if err != nil {
		panic(err)
	}
	mgmtReadyTimeout := 5 * time.Second

	defaultValue := true

	mockOIDCServer, err := authutils.MockOIDCRun()
	if err != nil {
		panic(err)
	}

	defer func() {
		err := mockOIDCServer.Shutdown()
		if err != nil {
			panic(err)
		}
	}()

	mockOIDCConfig := mockOIDCServer.Config()

	Convey("Verify mgmt auth info route enabled with htpasswd", t, func() {
		username, seedUser := test.GenerateRandomString()
		password, seedPass := test.GenerateRandomString()
		htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))

		defer func() {
			conf.HTTP.Auth.HTPasswd.Path = ""

			os.Remove(htpasswdPath)
		}()

		conf.HTTP.Auth.HTPasswd.Path = htpasswdPath

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)
		ctlr.Log.Info().Int64("seedUser", seedUser).Int64("seedPass", seedPass).Msg("random seed for username & password")

		subPaths := make(map[string]config.StorageConfig)
		subPaths["/a"] = config.StorageConfig{RootDirectory: t.TempDir()}

		ctlr.Config.Storage.RootDirectory = globalDir
		ctlr.Config.Storage.SubPaths = subPaths

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		So(err, ShouldBeNil)

		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		// without credentials
		resp, err := resty.R().Patch(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusMethodNotAllowed)

		// without credentials
		resp, err = resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd.Path, ShouldEqual, "")
		So(mgmtResp.HTTP.Auth.Bearer, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)

		// with credentials
		resp, err = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp = extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd.Path, ShouldEqual, "")
		So(mgmtResp.HTTP.Auth.Bearer, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)

		// with wrong credentials
		resp, err = resty.R().SetBasicAuth(username, "wrong").Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)
	})

	Convey("Verify mgmt auth info route enabled with ldap", t, func() {
		conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
			BaseDN:  "basedn",
			Address: "ldapexample",
		}).SetBindDN("binddn")

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)

		subPaths := make(map[string]config.StorageConfig)
		subPaths["/a"] = config.StorageConfig{RootDirectory: t.TempDir()}

		ctlr.Config.Storage.RootDirectory = t.TempDir()
		ctlr.Config.Storage.SubPaths = subPaths

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		// without credentials
		resp, err := resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd.Path, ShouldEqual, "")
		// ldap is always nil, htpasswd should be populated when ldap is used
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
	})

	Convey("Verify mgmt auth info route enabled with ldap + apikey", t, func() {
		conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
			BaseDN:  "basedn",
			Address: "ldapexample",
		}).SetBindDN("binddn")
		conf.HTTP.Auth.APIKey = true

		defer func() {
			conf.HTTP.Auth.APIKey = false
		}()

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)

		subPaths := make(map[string]config.StorageConfig)
		subPaths["/a"] = config.StorageConfig{RootDirectory: t.TempDir()}

		ctlr.Config.Storage.RootDirectory = t.TempDir()
		ctlr.Config.Storage.SubPaths = subPaths

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		// without credentials
		resp, err := resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd.Path, ShouldEqual, "")
		// ldap is always nil, htpasswd should be populated when ldap is used
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeTrue)
	})

	Convey("Verify mgmt auth info route enabled with htpasswd + ldap", t, func() {
		username, seedUser := test.GenerateRandomString()
		password, seedPass := test.GenerateRandomString()
		htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))

		defer func() {
			conf.HTTP.Auth.HTPasswd.Path = ""

			os.Remove(htpasswdPath)
		}()

		conf.HTTP.Auth.HTPasswd.Path = htpasswdPath
		conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
			BaseDN:  "basedn",
			Address: "ldapexample",
		}).SetBindDN("binddn")

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)
		ctlr.Log.Info().Int64("seedUser", seedUser).Int64("seedPass", seedPass).Msg("random seed for username & password")

		subPaths := make(map[string]config.StorageConfig)
		subPaths["/a"] = config.StorageConfig{RootDirectory: t.TempDir()}

		ctlr.Config.Storage.RootDirectory = t.TempDir()
		ctlr.Config.Storage.SubPaths = subPaths

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		// without credentials
		resp, err := resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd.Path, ShouldEqual, "")
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)

		// with credentials
		resp, err = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp = extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd.Path, ShouldEqual, "")
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
	})

	Convey("Verify mgmt auth info route enabled with htpasswd + ldap + bearer", t, func() {
		username, seedUser := test.GenerateRandomString()
		password, seedPass := test.GenerateRandomString()
		htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))

		defer func() {
			conf.HTTP.Auth.HTPasswd.Path = ""

			os.Remove(htpasswdPath)
		}()

		conf.HTTP.Auth.HTPasswd.Path = htpasswdPath
		conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
			BaseDN:  "basedn",
			Address: "ldapexample",
		}).SetBindDN("binddn")

		conf.HTTP.Auth.Bearer = &config.BearerConfig{
			Realm:   "realm",
			Service: "service",
		}

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)
		ctlr.Log.Info().Int64("seedUser", seedUser).Int64("seedPass", seedPass).Msg("random seed for username & password")

		ctlr.Config.Storage.RootDirectory = t.TempDir()

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		// without credentials
		resp, err := resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd.Path, ShouldEqual, "")
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, "realm")
		So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, "service")
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)

		// with credentials
		resp, err = resty.R().SetBasicAuth(username, password).Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp = extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd.Path, ShouldEqual, "")
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, "realm")
		So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, "service")
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
	})

	Convey("Verify mgmt auth info route enabled with ldap + bearer", t, func() {
		conf.HTTP.Auth.HTPasswd.Path = ""
		conf.HTTP.Auth.LDAP = (&config.LDAPConfig{
			BaseDN:  "basedn",
			Address: "ldapexample",
		}).SetBindDN("binddn")

		conf.HTTP.Auth.Bearer = &config.BearerConfig{
			Realm:   "realm",
			Service: "service",
		}

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)

		subPaths := make(map[string]config.StorageConfig)
		subPaths["/a"] = config.StorageConfig{RootDirectory: t.TempDir()}

		ctlr.Config.Storage.RootDirectory = t.TempDir()
		ctlr.Config.Storage.SubPaths = subPaths

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		// without credentials
		resp, err := resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd.Path, ShouldEqual, "")
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, "realm")
		So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, "service")
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
	})

	Convey("Verify mgmt auth info route enabled with bearer", t, func() {
		conf.HTTP.Auth.HTPasswd.Path = ""
		conf.HTTP.Auth.LDAP = nil
		conf.HTTP.Auth.Bearer = &config.BearerConfig{
			Realm:   "realm",
			Service: "service",
		}

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)

		ctlr.Config.Storage.RootDirectory = t.TempDir()

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		// without credentials
		resp, err := resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, "realm")
		So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, "service")
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
	})

	Convey("Verify mgmt auth info route enabled with openID", t, func() {
		conf.HTTP.Auth.HTPasswd.Path = ""
		conf.HTTP.Auth.LDAP = nil
		conf.HTTP.Auth.Bearer = nil

		openIDProviders := make(map[string]config.OpenIDProviderConfig)
		openIDProviders["oidc"] = config.OpenIDProviderConfig{
			ClientID:     mockOIDCConfig.ClientID,
			ClientSecret: mockOIDCConfig.ClientSecret,
			Issuer:       mockOIDCConfig.Issuer,
		}

		conf.HTTP.Auth.OpenID = &config.OpenIDConfig{
			Providers: openIDProviders,
		}

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)

		ctlr.Config.Storage.RootDirectory = t.TempDir()

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		// without credentials
		resp, err := resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		t.Logf("resp: %v", mgmtResp.HTTP.Auth.OpenID)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.OpenID, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.OpenID.Providers, ShouldNotBeEmpty)
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
	})

	Convey("Verify mgmt auth info route enabled with empty openID provider list", t, func() {
		username, seedUser := test.GenerateRandomString()
		password, seedPass := test.GenerateRandomString()
		htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password))

		defer func() {
			conf.HTTP.Auth.HTPasswd.Path = ""

			os.Remove(htpasswdPath)
		}()

		conf.HTTP.Auth.HTPasswd.Path = htpasswdPath
		conf.HTTP.Auth.LDAP = nil
		conf.HTTP.Auth.Bearer = nil

		openIDProviders := make(map[string]config.OpenIDProviderConfig)

		conf.HTTP.Auth.OpenID = &config.OpenIDConfig{
			Providers: openIDProviders,
		}

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)
		ctlr.Log.Info().Int64("seedUser", seedUser).Int64("seedPass", seedPass).Msg("random seed for username & password")

		ctlr.Config.Storage.RootDirectory = t.TempDir()

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		// without credentials
		resp, err := resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		t.Logf("resp: %v", mgmtResp.HTTP.Auth.OpenID)
		So(err, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldNotBeNil)
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.OpenID, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
	})

	Convey("Verify mgmt auth info route enabled without any auth", t, func() {
		globalDir := t.TempDir()
		conf := config.New()
		port := test.GetFreePort()
		conf.HTTP.Port = port
		baseURL := test.GetBaseURL(port)

		logFile, err := os.CreateTemp(globalDir, "zot-log*.txt")
		So(err, ShouldBeNil)
		defaultValue := true

		conf.Commit = "v1.0.0"

		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultValue
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultValue

		conf.Log.Output = logFile.Name()
		defer os.Remove(logFile.Name()) // cleanup

		ctlr := api.NewController(conf)

		ctlr.Config.Storage.RootDirectory = t.TempDir()

		ctlrManager := test.NewControllerManager(ctlr)
		ctlrManager.StartAndWait(port)
		defer ctlrManager.StopServer()

		resp, err := resty.R().Get(baseURL + constants.FullMgmt)
		So(err, ShouldBeNil)
		So(resp.StatusCode(), ShouldEqual, http.StatusOK)

		mgmtResp := extensions.StrippedConfig{}
		err = json.Unmarshal(resp.Body(), &mgmtResp)
		So(err, ShouldBeNil)
		So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion)
		So(mgmtResp.HTTP.Auth.Bearer, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
		So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)

		found, err := test.ReadLogFileAndSearchString(logFile.Name(),
			"setting up mgmt routes", mgmtReadyTimeout)
		defer func() {
			if !found {
				data, err := os.ReadFile(logFile.Name())
				So(err, ShouldBeNil)
				t.Log(string(data))
			}
		}()
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)

		found, err = test.ReadLogFileAndSearchString(logFile.Name(),
			"finished setting up mgmt routes", mgmtReadyTimeout)
		So(found, ShouldBeTrue)
		So(err, ShouldBeNil)
	})
}

func TestMgmtWithBearer(t *testing.T) {
	testCases := []struct {
		name                    string
		useLegacyAuthTestServer bool
	}{
		{
			name:                    "new authentication test server",
			useLegacyAuthTestServer: false,
		},
		{
			name:                    "legacy authentication test server",
			useLegacyAuthTestServer: true,
		},
	}

	for _, testCase := range testCases {
		Convey("Make a new controller with "+testCase.name, t, func() {
			authorizedNamespace := "allowedrepo"
			unauthorizedNamespace := "notallowedrepo"

			var authTestServer *httptest.Server
			if testCase.useLegacyAuthTestServer {
				authTestServer = authutils.MakeAuthTestServerLegacy(ServerKey, unauthorizedNamespace)
			} else {
				authTestServer = authutils.MakeAuthTestServer(ServerKey, "RS256", unauthorizedNamespace)
			}
			defer authTestServer.Close()

			port := test.GetFreePort()
			baseURL := test.GetBaseURL(port)

			conf := config.New()
			conf.HTTP.Port = port

			aurl, err := url.Parse(authTestServer.URL)
			So(err, ShouldBeNil)

			conf.HTTP.Auth = &config.AuthConfig{
				Bearer: &config.BearerConfig{
					Cert:    ServerCert,
					Realm:   authTestServer.URL + "/auth/token",
					Service: aurl.Host,
				},
			}

			defaultValue := true

			conf.Extensions = &extconf.ExtensionConfig{}
			conf.Extensions.Search = &extconf.SearchConfig{}
			conf.Extensions.Search.Enable = &defaultValue
			conf.Extensions.Search.CVE = nil
			conf.Extensions.UI = &extconf.UIConfig{}
			conf.Extensions.UI.Enable = &defaultValue

			conf.Storage.RootDirectory = t.TempDir()

			ctlr := api.NewController(conf)

			cm := test.NewControllerManager(ctlr)
			cm.StartAndWait(port)
			defer cm.StopServer()

			resp, err := resty.R().Get(baseURL + "/v2/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)

			authorizationHeader := authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
			resp, err = resty.R().
				SetQueryParam("service", authorizationHeader.Service).
				SetQueryParam("scope", authorizationHeader.Scope).
				Get(authorizationHeader.Realm)
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			var goodToken authutils.AccessTokenResponse

			err = json.Unmarshal(resp.Body(), &goodToken)
			So(err, ShouldBeNil)

			resp, err = resty.R().
				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
				Get(baseURL + "/v2/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			resp, err = resty.R().SetHeader("Authorization",
				"Bearer "+goodToken.AccessToken).Options(baseURL + "/v2/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)

			resp, err = resty.R().Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)

			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
			resp, err = resty.R().
				SetQueryParam("service", authorizationHeader.Service).
				SetQueryParam("scope", authorizationHeader.Scope).
				Get(authorizationHeader.Realm)
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
			err = json.Unmarshal(resp.Body(), &goodToken)
			So(err, ShouldBeNil)

			resp, err = resty.R().
				SetHeader("Authorization", "Bearer "+goodToken.AccessToken).
				Post(baseURL + "/v2/" + authorizedNamespace + "/blobs/uploads/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)

			resp, err = resty.R().
				Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)

			authorizationHeader = authutils.ParseBearerAuthHeader(resp.Header().Get("WWW-Authenticate"))
			resp, err = resty.R().
				SetQueryParam("service", authorizationHeader.Service).
				SetQueryParam("scope", authorizationHeader.Scope).
				Get(authorizationHeader.Realm)
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			var badToken authutils.AccessTokenResponse

			err = json.Unmarshal(resp.Body(), &badToken)
			So(err, ShouldBeNil)

			resp, err = resty.R().
				SetHeader("Authorization", "Bearer "+badToken.AccessToken).
				Post(baseURL + "/v2/" + unauthorizedNamespace + "/blobs/uploads/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)

			// test mgmt route
			resp, err = resty.R().Get(baseURL + constants.FullMgmt)
			So(err, ShouldBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			mgmtResp := extensions.StrippedConfig{}
			err = json.Unmarshal(resp.Body(), &mgmtResp)
			So(err, ShouldBeNil)
			So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion)
			So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
			So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm)
			So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service)
			So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
			So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
			So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)

			resp, err = resty.R().SetBasicAuth("", "").Get(baseURL + constants.FullMgmt)
			So(err, ShouldBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			mgmtResp = extensions.StrippedConfig{}
			err = json.Unmarshal(resp.Body(), &mgmtResp)
			So(err, ShouldBeNil)
			So(mgmtResp.DistSpecVersion, ShouldResemble, conf.DistSpecVersion)
			So(mgmtResp.HTTP.Auth.Bearer, ShouldNotBeNil)
			So(mgmtResp.HTTP.Auth.Bearer.Realm, ShouldEqual, conf.HTTP.Auth.Bearer.Realm)
			So(mgmtResp.HTTP.Auth.Bearer.Service, ShouldEqual, conf.HTTP.Auth.Bearer.Service)
			So(mgmtResp.HTTP.Auth.HTPasswd, ShouldBeNil)
			So(mgmtResp.HTTP.Auth.LDAP, ShouldBeNil)
			So(mgmtResp.HTTP.Auth.APIKey, ShouldBeFalse)
		})
	}
}

func TestAllowedMethodsHeaderMgmt(t *testing.T) {
	defaultVal := true

	Convey("Test http options response", t, func() {
		conf := config.New()
		port := test.GetFreePort()
		conf.HTTP.Port = port
		conf.Extensions = &extconf.ExtensionConfig{}
		conf.Extensions.Search = &extconf.SearchConfig{}
		conf.Extensions.Search.Enable = &defaultVal
		conf.Extensions.Search.CVE = nil
		conf.Extensions.UI = &extconf.UIConfig{}
		conf.Extensions.UI.Enable = &defaultVal

		baseURL := test.GetBaseURL(port)

		ctlr := api.NewController(conf)
		ctlr.Config.Storage.RootDirectory = t.TempDir()

		ctrlManager := test.NewControllerManager(ctlr)

		ctrlManager.StartAndWait(port)
		defer ctrlManager.StopServer()

		resp, _ := resty.R().Options(baseURL + constants.FullMgmt)
		So(resp, ShouldNotBeNil)
		So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "GET,OPTIONS")
		So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
	})
}
