Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions internal/debugtools/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Package debugtools contains tools used for debugging the application.
package debugtools

import (
"net/http"
"net/http/httputil"

slogctx "github.com/veqryn/slog-context"
)

// transport is a wrapper for an http.RoundTripper which logs the request and response dumps.
type transport struct {
base http.RoundTripper
}

func NewTransport(base http.RoundTripper) http.RoundTripper {
return &transport{
base: base,
}
}

func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
ctx := req.Context()
reqDump, _ := httputil.DumpRequestOut(req, true)
ctx = slogctx.With(ctx, "request", reqDump)
resp, err := t.base.RoundTrip(req)
if err != nil {
slogctx.Debug(ctx, "http request executed with an error")
} else {
respDump, _ := httputil.DumpResponse(resp, true)
slogctx.Debug(ctx, "http request executed successfully", "response", respDump)
}

return resp, err
}
58 changes: 58 additions & 0 deletions internal/debugtools/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package debugtools

import (
"errors"
"net/http"
"net/http/httptest"
"reflect"
"testing"
)

type dummyRoundTripper struct {
resp *http.Response
err error
}

func (rt dummyRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
return rt.resp, rt.err
}

func Test_transport_RoundTrip(t *testing.T) {
const url = "http://localhost"
resp := httptest.NewRecorder().Result()
tests := []struct {
name string
base http.RoundTripper
req *http.Request
want *http.Response
wantErr bool
}{
{
name: "Round trip",
base: dummyRoundTripper{resp: resp, err: nil},
req: httptest.NewRequest(http.MethodGet, url, nil),
want: resp,
wantErr: false,
},
{
name: "Return an error",
base: dummyRoundTripper{resp: nil, err: errors.New("err")},
req: httptest.NewRequest(http.MethodGet, url, nil),
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tr := NewTransport(tt.base)
got, err := tr.RoundTrip(tt.req)
if (err != nil) != tt.wantErr {
t.Errorf("transport.RoundTrip() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("transport.RoundTrip() = %v, want %v", got, tt.want)
}
})
}
}
81 changes: 81 additions & 0 deletions internal/debugtools/settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package debugtools

import (
"os"
"sync"
"sync/atomic"
)

var cache sync.Map // name string -> value *setting
var empty string

type Setting struct {
*setting

name string
once sync.Once
}

type setting struct {
value atomic.Pointer[string]
}

func NewSetting(name string) *Setting {
return &Setting{name: name}
}

func (s *Setting) Name() string {
return s.name
}

func (s *Setting) Value() string {
s.once.Do(func() {
s.setting = lookup(s.Name())
})

return *s.value.Load()
}

func (s *Setting) String() string {
return s.Name() + "=" + s.Value()
}

func lookup(name string) *setting {
if v, ok := cache.Load(name); ok {
//nolint:forcetypeassert
return v.(*setting)
}

s := new(setting)
s.value.Store(&empty)
if v, loaded := cache.LoadOrStore(name, s); loaded {
//nolint:forcetypeassert
return v.(*setting)
}

return s
}

func init() {
env := os.Getenv("DEBUGFEATURES")
parse(env)
}

// parse parses DEBUGFEATURES environment variable in the form of k1=v1,k2=v2,k3=v3
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: typo, double parses

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't a typo. It was the format 'function_name (aka parse)' does (parses) something.

// and stores the keys.
func parse(s string) {
end := len(s)
eq := -1
for i := end - 1; i >= -1; i-- {
if i == -1 || s[i] == ',' {
if eq >= 0 {
name, arg := s[i+1:eq], s[eq+1:end]
lookup(name).value.Store(&arg)
}
eq = -1
end = i
} else if s[i] == '=' {
eq = i
}
}
}
82 changes: 82 additions & 0 deletions internal/debugtools/settings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package debugtools

import (
"testing"
)

func TestSetting_Name(t *testing.T) {
tests := []struct {
name string
setting *Setting
want string
}{
{
name: "Get setting name",
setting: NewSetting("testsetting"),
want: "testsetting",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.setting.Name(); got != tt.want {
t.Errorf("Setting.Name() = %v, want %v", got, tt.want)
}
})
}
}

func TestSetting_Value(t *testing.T) {
tests := []struct {
name string
newSetting func() *Setting
want string
}{
{
name: "Read setting value",
newSetting: func() *Setting {
const featureName = "testfeature2"
const env = "testfeature1=1," + featureName + "=somevalue,testfeature3=3"

parse(env)
return NewSetting(featureName)
},
want: "somevalue",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := tt.newSetting()
if got := s.Value(); got != tt.want {
t.Errorf("Setting.Value() = %v, want %v", got, tt.want)
}
})
}
}

func TestSetting_String(t *testing.T) {
tests := []struct {
name string
newSetting func() *Setting
want string
}{
{
name: "Setting kv",
newSetting: func() *Setting {
const featureName = "testfeature2"
const env = "testfeature1=1," + featureName + "=somevalue,testfeature3=3"

parse(env)
return NewSetting(featureName)
},
want: "testfeature2=somevalue",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := tt.newSetting()
if got := s.String(); got != tt.want {
t.Errorf("Setting.String() = %v, want %v", got, tt.want)
}
})
}
}
10 changes: 9 additions & 1 deletion internal/grpc/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ import (
grpccodes "google.golang.org/grpc/codes"

"github.com/openkcm/session-manager/internal/credentials"
"github.com/openkcm/session-manager/internal/debugtools"
"github.com/openkcm/session-manager/internal/session"
"github.com/openkcm/session-manager/internal/trust"
)

var debugSettingSMDumpTransport = debugtools.NewSetting("smdumptransport")

type SessionServer struct {
sessionv1.UnimplementedServiceServer

Expand Down Expand Up @@ -211,8 +214,13 @@ func (s *SessionServer) getClientID(mapping *trust.OIDCMapping) string {

func (s *SessionServer) httpClient(mapping *trust.OIDCMapping) *http.Client {
creds := s.newCreds(s.getClientID(mapping))
transport := creds.Transport()
if debugSettingSMDumpTransport.Value() == "1" {
transport = debugtools.NewTransport(transport)
}

return &http.Client{
Transport: creds.Transport(),
Transport: transport,
}
}

Expand Down
10 changes: 9 additions & 1 deletion internal/session/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ import (

"github.com/openkcm/session-manager/internal/config"
"github.com/openkcm/session-manager/internal/credentials"
"github.com/openkcm/session-manager/internal/debugtools"
"github.com/openkcm/session-manager/internal/pkce"
"github.com/openkcm/session-manager/internal/serviceerr"
"github.com/openkcm/session-manager/internal/trust"
)

var debugSettingSMDumpTransport = debugtools.NewSetting("smdumptransport")

const (
LoginCSRFCookieName = "LoginCSRF"
)
Expand Down Expand Up @@ -610,8 +613,13 @@ func (m *Manager) verifyAccessToken(accessToken, atHash string, idToken *jwt.JSO

func (m *Manager) httpClient(mapping trust.OIDCMapping) *http.Client {
creds := m.newCreds(m.getClientID(mapping))
transport := creds.Transport()
if debugSettingSMDumpTransport.Value() == "1" {
transport = debugtools.NewTransport(transport)
}

return &http.Client{
Transport: creds.Transport(),
Transport: transport,
}
}

Expand Down
Loading