// Package silly provides a simple authentication scheme that checks for the
// existence of an Authorization header and issues access if is present and
// non-empty.
//
// This package is present as an example implementation of a minimal
// auth.AccessController and for testing. This is not suitable for any kind of
// production security.
package silly

import (
	"context"
	"fmt"
	"net/http"
	"strings"

	dcontext "github.com/docker/distribution/context"
	"github.com/docker/distribution/registry/auth"
)

// accessController provides a simple implementation of auth.AccessController
// that simply checks for a non-empty Authorization header. It is useful for
// demonstration and testing.
type accessController struct {
	realm   string
	service string
}

var _ auth.AccessController = &accessController{}

func newAccessController(options map[string]interface{}) (auth.AccessController, error) {
	realm, present := options["realm"]
	if _, ok := realm.(string); !present || !ok {
		return nil, fmt.Errorf(`"realm" must be set for silly access controller`)
	}

	service, present := options["service"]
	if _, ok := service.(string); !present || !ok {
		return nil, fmt.Errorf(`"service" must be set for silly access controller`)
	}

	return &accessController{realm: realm.(string), service: service.(string)}, nil
}

// Authorized simply checks for the existence of the authorization header,
// responding with a bearer challenge if it doesn't exist.
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
	req, err := dcontext.GetRequest(ctx)
	if err != nil {
		return nil, err
	}

	if req.Header.Get("Authorization") == "" {
		challenge := challenge{
			realm:   ac.realm,
			service: ac.service,
		}

		if len(accessRecords) > 0 {
			var scopes []string
			for _, access := range accessRecords {
				scopes = append(scopes, fmt.Sprintf("%s:%s:%s", access.Type, access.Resource.Name, access.Action))
			}
			challenge.scope = strings.Join(scopes, " ")
		}

		return nil, &challenge
	}

	ctx = auth.WithUser(ctx, auth.UserInfo{Name: "silly"})
	ctx = dcontext.WithLogger(ctx, dcontext.GetLogger(ctx, auth.UserNameKey, auth.UserKey))

	return ctx, nil

}

type challenge struct {
	realm   string
	service string
	scope   string
}

var _ auth.Challenge = challenge{}

// SetHeaders sets a simple bearer challenge on the response.
func (ch challenge) SetHeaders(r *http.Request, w http.ResponseWriter) {
	header := fmt.Sprintf("Bearer realm=%q,service=%q", ch.realm, ch.service)

	if ch.scope != "" {
		header = fmt.Sprintf("%s,scope=%q", header, ch.scope)
	}

	w.Header().Set("WWW-Authenticate", header)
}

func (ch challenge) Error() string {
	return fmt.Sprintf("silly authentication challenge: %#v", ch)
}

// init registers the silly auth backend.
func init() {
	auth.Register("silly", auth.InitFunc(newAccessController))
}
