Http: add pluggable authentication and token (#20066)

This commit is contained in:
andig 2025-03-27 12:35:13 +01:00 • committed by GitHub
parent 6f5c0b6478
commit 986772d525
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 140 additions and 48 deletions

7
plugin/auth/api.go Normal file
View file

@ -0,0 +1,7 @@
package auth
import "net/http"
type Authorizer interface {
Transport(base http.RoundTripper) (http.RoundTripper, error)
}

26
plugin/auth/config.go Normal file
View file

@ -0,0 +1,26 @@
package auth
import (
"context"
"fmt"
"strings"
reg "github.com/evcc-io/evcc/util/registry"
)
var registry = reg.New[Authorizer]("auth")
// NewFromConfig creates auth from configuration
func NewFromConfig(ctx context.Context, typ string, other map[string]any) (Authorizer, error) {
factory, err := registry.Get(strings.ToLower(typ))
if err != nil {
return nil, err
}
v, err := factory(ctx, other)
if err != nil {
err = fmt.Errorf("cannot create auth type '%s': %w", typ, err)
}
return v, err
}

27
plugin/auth/nop.go Normal file
View file

@ -0,0 +1,27 @@
package auth
import (
"context"
"net/http"
"github.com/evcc-io/evcc/util"
)
type nop struct{}
func init() {
registry.AddCtx("nop", NewNopFromConfig)
}
func NewNopFromConfig(ctx context.Context, other map[string]any) (Authorizer, error) {
var cc struct{}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return new(nop), nil
}
func (p *nop) Transport(base http.RoundTripper) (http.RoundTripper, error) {
return base, nil
}

View file

@ -14,7 +14,6 @@ import (
"github.com/evcc-io/evcc/util/request"
"github.com/evcc-io/evcc/util/transport"
"github.com/gregjones/httpcache"
"github.com/jpfielding/go-http-digest/pkg/digest"
)
// HTTP implements HTTP request provider
@ -33,11 +32,6 @@ func init() {
var mc = httpcache.NewMemoryCache()
// Auth is the authorization config
type Auth struct {
Type, User, Password string
}
// NewHTTPPluginFromConfig creates a HTTP provider
func NewHTTPPluginFromConfig(ctx context.Context, other map[string]interface{}) (Plugin, error) {
cc := struct {
@ -80,18 +74,21 @@ func NewHTTPPluginFromConfig(ctx context.Context, other map[string]interface{})
p.getter = defaultGetters(p, cc.Scale)
var err error
if cc.Auth.Type != "" {
_, err = p.WithAuth(cc.Auth.Type, cc.Auth.User, cc.Auth.Password)
if cc.Auth.Type != "" || cc.Auth.Source != "" {
transport, err := cc.Auth.Transport(ctx, p.Client.Transport)
if err != nil {
return nil, err
}
p.Client.Transport = transport
}
if err == nil {
var pipe *pipeline.Pipeline
pipe, err = pipeline.New(log, cc.Settings)
p = p.WithPipeline(pipe)
pipe, err := pipeline.New(log, cc.Settings)
if err != nil {
return nil, err
}
p.pipeline = pipe
return p, err
return p, nil
}
// NewHTTP create HTTP provider
@ -128,7 +125,12 @@ func NewHTTP(log *util.Logger, method, uri string, insecure bool, cache time.Dur
// WithBody adds request body
func (p *HTTP) WithBody(body string) *HTTP {
p.body = body
if body != "" {
p.body = body
if p.method == http.MethodGet {
p.method = http.MethodPost
}
}
return p
}
@ -138,28 +140,6 @@ func (p *HTTP) WithHeaders(headers map[string]string) *HTTP {
return p
}
// WithPipeline adds a processing pipeline
func (p *HTTP) WithPipeline(pipeline *pipeline.Pipeline) *HTTP {
p.pipeline = pipeline
return p
}
// WithAuth adds authorized transport
func (p *HTTP) WithAuth(typ, user, password string) (*HTTP, error) {
switch strings.ToLower(typ) {
case "basic":
p.Client.Transport = transport.BasicAuth(user, password, p.Client.Transport)
case "bearer":
p.Client.Transport = transport.BearerAuth(password, p.Client.Transport)
case "digest":
p.Client.Transport = digest.NewTransport(user, password, p.Client.Transport)
default:
return nil, fmt.Errorf("unknown auth type '%s'", typ)
}
return p, nil
}
// request executes the configured request or returns the cached value
func (p *HTTP) request(url string, body string) ([]byte, error) {
var b io.Reader

52
plugin/http_auth.go Normal file
View file

@ -0,0 +1,52 @@
package plugin
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/evcc-io/evcc/plugin/auth"
"github.com/evcc-io/evcc/util/transport"
"github.com/jpfielding/go-http-digest/pkg/digest"
)
// Auth is the authorization config
type Auth struct {
Type, User, Password, Token string
Source string
Other map[string]any `mapstructure:",remain"`
}
func (p *Auth) Transport(ctx context.Context, base http.RoundTripper) (http.RoundTripper, error) {
switch strings.ToLower(p.Type) {
case "digest":
return digest.NewTransport(p.User, p.Password, base), nil
case "basic":
return transport.BasicAuth(p.User, p.Password, base), nil
case "bearer":
return transport.BearerAuth(p.Token, base), nil
default:
if p.Source == "" {
return nil, fmt.Errorf("unknown auth type '%s'", p.Type)
}
if p.User != "" {
p.Other["user"] = p.User
}
if p.Password != "" {
p.Other["password"] = p.Password
}
authorizer, err := auth.NewFromConfig(ctx, p.Source, p.Other)
if err != nil {
return nil, err
}
return authorizer.Transport(base)
}
}