//go:build profile
// +build profile

package pprof_test

import (
	"net/http"
	"os"
	"testing"

	. "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"
	debugConstants "zotregistry.dev/zot/pkg/debug/constants"
	test "zotregistry.dev/zot/pkg/test/common"
)

func TestProfilingAuthz(t *testing.T) {
	Convey("Make a new controller", t, func() {
		port := test.GetFreePort()
		baseURL := test.GetBaseURL(port)
		adminUsername, seedAdminUser := test.GenerateRandomString()
		adminPassword, seedAdminPass := test.GenerateRandomString()
		username, seedUser := test.GenerateRandomString()
		password, seedPass := test.GenerateRandomString()
		authorizationAllRepos := test.AuthorizationAllRepos

		testCreds := test.GetCredString(adminUsername, adminPassword) +
			test.GetCredString(username, password)
		htpasswdPath := test.MakeHtpasswdFileFromString(testCreds)

		defer os.Remove(htpasswdPath)

		conf := config.New()
		conf.HTTP.Port = port
		conf.Storage.RootDirectory = t.TempDir()

		Convey("Test with no access control", func() {
			ctlr := api.NewController(conf)
			cm := test.NewControllerManager(ctlr)
			cm.StartAndWait(port)

			defer cm.StopServer()

			// unauthenticated clients should have access to /v2/
			resp, err := resty.R().Get(baseURL + "/v2/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			// unauthenticated clients should have access to the profiling endpoints
			resp, err = resty.R().Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint + "trace")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			resp, err = resty.R().SetQueryParam("seconds", "1").
				Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint + "profile")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			resp, err = resty.R().Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint + "goroutine")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			// test building the index
			resp, err = resty.R().Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint)
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
		})

		Convey("Test with authenticated users and no anonymous policy", func() {
			conf.HTTP.Auth = &config.AuthConfig{
				HTPasswd: config.AuthHTPasswd{
					Path: htpasswdPath,
				},
			}
			conf.HTTP.AccessControl = &config.AccessControlConfig{
				Repositories: config.Repositories{
					authorizationAllRepos: config.PolicyGroup{
						Policies: []config.Policy{
							{
								Users:   []string{username},
								Actions: []string{"read", "create"},
							},
						},
						DefaultPolicy: []string{},
					},
				},
				AdminPolicy: config.Policy{
					Users:   []string{adminUsername},
					Actions: []string{},
				},
			}

			ctlr := api.NewController(conf)
			ctlr.Log.Info().Int64("seedAdminUser", seedAdminUser).Int64("seedAdminPass", seedAdminPass).
				Msg("random seed for admin username & password")
			ctlr.Log.Info().Int64("seedUser", seedUser).Int64("seedPass", seedPass).Msg("random seed for username & password")
			cm := test.NewControllerManager(ctlr)
			cm.StartAndWait(port)

			defer cm.StopServer()

			// unauthenticated clients should not have access to /v2/
			resp, err := resty.R().Get(baseURL + "/v2/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)

			// unauthenticated clients should not have access to the profiling endpoint
			resp, err = resty.R().Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint + "trace")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)

			// authenticated clients without permissions should not have access to the profiling endpoint
			resp, err = resty.R().SetBasicAuth(username, password).
				Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint + "trace")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)

			// authenticated clients with admin permissions should have access to the profiling endpoint
			resp, err = resty.R().SetBasicAuth(adminUsername, adminPassword).
				Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint + "trace")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
		})

		Convey("Test with authenticated users and anonymous policy", func() {
			conf.HTTP.Auth = &config.AuthConfig{
				HTPasswd: config.AuthHTPasswd{
					Path: htpasswdPath,
				},
			}
			conf.HTTP.AccessControl = &config.AccessControlConfig{
				Repositories: config.Repositories{
					authorizationAllRepos: config.PolicyGroup{
						Policies: []config.Policy{
							{
								Users:   []string{username},
								Actions: []string{"read", "create"},
							},
						},
						DefaultPolicy:   []string{},
						AnonymousPolicy: []string{"read"},
					},
				},
				AdminPolicy: config.Policy{
					Users:   []string{adminUsername},
					Actions: []string{},
				},
			}

			ctlr := api.NewController(conf)
			cm := test.NewControllerManager(ctlr)
			cm.StartAndWait(port)

			defer cm.StopServer()

			// unauthenticated clients should have access to /v2/
			resp, err := resty.R().Get(baseURL + "/v2/")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)

			// unauthenticated clients should not have access to the profiling endpoint
			resp, err = resty.R().Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint + "trace")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusUnauthorized)

			// authenticated clients without permissions should not have access to the profiling endpoint
			resp, err = resty.R().SetBasicAuth(username, password).
				Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint + "trace")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusForbidden)

			// authenticated clients with admin permissions should have access to the profiling endpoint
			resp, err = resty.R().SetBasicAuth(adminUsername, adminPassword).
				Get(baseURL + constants.RoutePrefix + debugConstants.ProfilingEndpoint + "trace")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.StatusCode(), ShouldEqual, http.StatusOK)
		})
	})
}
