diff --git a/go.mod b/go.mod index 52e453de7b..65fb15a312 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( github.com/kovidgoyal/imaging v1.6.3 github.com/leonelquinteros/gotext v1.6.1 github.com/libregraph/idm v0.5.0 - github.com/libregraph/lico v0.62.0 + github.com/libregraph/lico v0.64.0 github.com/mitchellh/mapstructure v1.5.0 github.com/mna/pigeon v1.3.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 @@ -78,7 +78,7 @@ require ( github.com/r3labs/sse/v2 v2.10.0 github.com/riandyrn/otelchi v0.10.0 github.com/rogpeppe/go-internal v1.13.1 - github.com/rs/cors v1.11.0 + github.com/rs/cors v1.11.1 github.com/rs/zerolog v1.33.0 github.com/shamaton/msgpack/v2 v2.2.2 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index d5ccbc855d..30d83cd571 100644 --- a/go.sum +++ b/go.sum @@ -782,8 +782,8 @@ github.com/leonelquinteros/gotext v1.6.1 h1:PuTN8YUqHvfPZxW+fPXp7o0Fc2zN9L2wXBZr github.com/leonelquinteros/gotext v1.6.1/go.mod h1:qQRISjoonXYFdRGrTG1LARQ38Gpibad0IPeB4hpvyyM= github.com/libregraph/idm v0.5.0 h1:tDMwKbAOZzdeDYMxVlY5PbSqRKO7dbAW9KT42A51WSk= github.com/libregraph/idm v0.5.0/go.mod h1:BGMwIQ/6orJSPVzJ1x6kgG2JyG9GY05YFmbsnaD80k0= -github.com/libregraph/lico v0.62.0 h1:4aa0hp8kLCj/oy/aGuGKQCzdQRLHY/cvDs25bdkdcXU= -github.com/libregraph/lico v0.62.0/go.mod h1:Byuq0dALiJD9hpx2gvX5UTVZ6eWZs3sO0SeoXQQeFcE= +github.com/libregraph/lico v0.64.0 h1:fbMV2ALjrOysGL0m58bhRrF+9e/HCL5RkoSwMN+xoWQ= +github.com/libregraph/lico v0.64.0/go.mod h1:J2ZNe1DcO+K/5ptOOrQk2A2mn6OwXRdGUI4ASgw2WGg= github.com/libregraph/oidc-go v1.1.0 h1:RyudjL3UyQblqeBQI06W53PniWobqODeeyAy6v/HumA= github.com/libregraph/oidc-go v1.1.0/go.mod h1:qW9ubcXvZrfbbWZBaLMuk7bt5qAUMYyt9/NtXQt07Cw= github.com/linode/linodego v0.25.3/go.mod h1:GSBKPpjoQfxEfryoCRcgkuUOCuVtGHWhzI8OMdycNTE= @@ -1052,8 +1052,8 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po= -github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= diff --git a/vendor/github.com/libregraph/lico/CHANGELOG.md b/vendor/github.com/libregraph/lico/CHANGELOG.md index 69c670360c..4e998708ae 100644 --- a/vendor/github.com/libregraph/lico/CHANGELOG.md +++ b/vendor/github.com/libregraph/lico/CHANGELOG.md @@ -4,6 +4,30 @@ +## v0.64.0 (2024-09-18) + +- Implement refresh and revoke for lg identifier backend session +- Pass real src ip and user agent to lg identifier backend +- Fix variable shadowing making error checks ineffective + + +## v0.63.0 (2024-09-10) + +- Bump semver from 5.7.1 to 5.7.2 in /identifier +- Ignore js license ranger border check warnings +- Fix js license ranger for new source-map-explorer +- Bump source-map-explorer to 2.5.3 in /identifier +- Update linter CI version +- Fix access token sid claim when provided via lg backend +- Bump google.golang.org/protobuf from 1.30.0 to 1.33.0 +- Bump github.com/rs/cors from 1.10.1 to 1.11.1 +- Add password visibility icon in login dialog +- Bump github.com/spf13/cobra from 1.7.0 to 1.8.1 +- Remove :443 from Host header for secure referrer/origin check +- Allow authorize requests wihout openid scope +- Bump github.com/gorilla/schema from 1.2.0 to 1.4.1 + + ## v0.62.0 (2024-05-08) - Update golangci-lint config diff --git a/vendor/github.com/libregraph/lico/Caddyfile.dev b/vendor/github.com/libregraph/lico/Caddyfile.dev index 38ff2b35e8..3e0d453ddf 100644 --- a/vendor/github.com/libregraph/lico/Caddyfile.dev +++ b/vendor/github.com/libregraph/lico/Caddyfile.dev @@ -12,13 +12,27 @@ tls self_signed # konnect oidc - proxy /.well-known/openid-configuration 127.0.0.1:8777 - proxy /konnect/v1/jwks.json 127.0.0.1:8777 - proxy /konnect/v1/token 127.0.0.1:8777 - proxy /konnect/v1/userinfo 127.0.0.1:8777 - proxy /konnect/v1/static 127.0.0.1:8777 - proxy /konnect/v1/session 127.0.0.1:8777 - proxy /konnect/v1/register 127.0.0.1:8777 + proxy /.well-known/openid-configuration 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/jwks.json 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/token 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/userinfo 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/static 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/session 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/register 127.0.0.1:8777 { + transparent + } # konnect identifier development via webpack-dev-server proxy /signin/v1/ 127.0.0.1:3001 { @@ -46,6 +60,7 @@ #proxy /provider/simple/konnect/v1/authorize 127.0.0.1:8777 { # without /provider/simple # header_upstream X-Forwarded-Prefix /provider/simple + # transparent #} # konnect cookieserver, start with python3 ./examples/cookieserver.py 8088 diff --git a/vendor/github.com/libregraph/lico/Caddyfile.example b/vendor/github.com/libregraph/lico/Caddyfile.example index 734d8f9cf0..9dc807a705 100644 --- a/vendor/github.com/libregraph/lico/Caddyfile.example +++ b/vendor/github.com/libregraph/lico/Caddyfile.example @@ -9,13 +9,27 @@ tls self_signed # konnect oidc - proxy /.well-known/openid-configuration 127.0.0.1:8777 - proxy /konnect/v1/jwks.json 127.0.0.1:8777 - proxy /konnect/v1/token 127.0.0.1:8777 - proxy /konnect/v1/userinfo 127.0.0.1:8777 - proxy /konnect/v1/static 127.0.0.1:8777 - proxy /konnect/v1/session 127.0.0.1:8777 - proxy /konnect/v1/register 127.0.0.1:8777 + proxy /.well-known/openid-configuration 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/jwks.json 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/token 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/userinfo 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/static 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/session 127.0.0.1:8777 { + transparent + } + proxy /konnect/v1/register 127.0.0.1:8777 { + transparent + } # konnect identifier login area proxy /signin/ 127.0.0.1:8777 { diff --git a/vendor/github.com/libregraph/lico/context.go b/vendor/github.com/libregraph/lico/context.go index c1906ac3ae..e755c1c507 100644 --- a/vendor/github.com/libregraph/lico/context.go +++ b/vendor/github.com/libregraph/lico/context.go @@ -19,6 +19,7 @@ package lico import ( "context" + "net/http" "github.com/golang-jwt/jwt/v4" ) @@ -30,7 +31,10 @@ type key int // claimsKey is the key for claims in contexts. It is // unexported; clients use konnect.NewClaimsContext and // connect.FromClaimsContext instead of using this key directly. -var claimsKey key +const ( + claimsKey key = iota + requestKey +) // NewClaimsContext returns a new Context that carries value auth. func NewClaimsContext(ctx context.Context, claims jwt.Claims) context.Context { @@ -42,3 +46,14 @@ func FromClaimsContext(ctx context.Context) (jwt.Claims, bool) { claims, ok := ctx.Value(claimsKey).(jwt.Claims) return claims, ok } + +// NewRequestContext returns a new Context that carries a request object. +func NewRequestContext(ctx context.Context, req *http.Request) context.Context { + return context.WithValue(ctx, requestKey, req) +} + +// FromRequestContext returns the Request object stored in ctx, if any. +func FromRequestContext(ctx context.Context) (*http.Request, bool) { + req, ok := ctx.Value(requestKey).(*http.Request) + return req, ok +} diff --git a/vendor/github.com/libregraph/lico/identifier/backends/libregraph/libregraph.go b/vendor/github.com/libregraph/lico/identifier/backends/libregraph/libregraph.go index 4ec90c3951..ad99909e4c 100644 --- a/vendor/github.com/libregraph/lico/identifier/backends/libregraph/libregraph.go +++ b/vendor/github.com/libregraph/lico/identifier/backends/libregraph/libregraph.go @@ -54,8 +54,9 @@ const ( ) const ( - apiPathMe = "/api/v1/me" - apiPathUsers = "/api/v1/users" + apiPathMe = "/api/v1/me" + apiPathUsers = "/api/v1/users" + apiPathRevokeSignInSession = "/api/v1/me/revokeSignInSession" ) var libreGraphSpportedScopes = []string{ @@ -66,6 +67,8 @@ var libreGraphSpportedScopes = []string{ } type LibreGraphIdentifierBackend struct { + config *config.Config + supportedScopes []string logger logrus.FieldLogger @@ -245,7 +248,7 @@ func (u *libreGraphUser) setRequiredScopes(selectedScope string, scopeMap *order } func (u *libreGraphUser) sessionID() string { - if accessTokenClaims, ok := u.identityClaims[""].(map[string]interface{}); ok { + if accessTokenClaims, ok := u.identityClaims[konnect.InternalExtraAccessTokenClaimsClaim].(map[string]interface{}); ok { if sessionID, withSessionID := accessTokenClaims[oidc.SessionIDClaim].(string); withSessionID { if sessionID != "" { return sessionID @@ -297,6 +300,8 @@ func NewLibreGraphIdentifierBackend( transport.IdleConnTimeout = 30 * time.Second b := &LibreGraphIdentifierBackend{ + config: c, + supportedScopes: supportedScopes, logger: c.Logger, @@ -348,16 +353,27 @@ func (b *LibreGraphIdentifierBackend) Logon(ctx context.Context, audience, usern } req.SetBasicAuth(username, password) + if record == nil { + record, _ = identifier.RecordFromRequestContext(ctx, b.config) + } if record != nil { // Inject HTTP headers. - if record.HelloRequest.Flow != "" { - req.Header.Set("X-Flow", record.HelloRequest.Flow) + if record.HelloRequest != nil { + if record.HelloRequest.Flow != "" { + req.Header.Set("X-Flow", record.HelloRequest.Flow) + } + if record.HelloRequest.RawScope != "" { + req.Header.Set("X-Scope", record.HelloRequest.RawScope) + } + if record.HelloRequest.RawPrompt != "" { + req.Header.Set("X-Prompt", record.HelloRequest.RawPrompt) + } } - if record.HelloRequest.RawScope != "" { - req.Header.Set("X-Scope", record.HelloRequest.RawScope) + if record.RealIP != "" { + req.Header.Set("X-User-Real-IP", record.RealIP) } - if record.HelloRequest.RawPrompt != "" { - req.Header.Set("X-Prompt", record.HelloRequest.RawPrompt) + if record.UserAgent != "" { + req.Header.Set("X-User-Real-User-Agent", record.UserAgent) } } req.Header.Set("User-Agent", utils.DefaultHTTPUserAgent) @@ -407,7 +423,7 @@ func (b *LibreGraphIdentifierBackend) Logon(ctx context.Context, audience, usern // Put the user into the record (if any). if record != nil { - record.UserFromBackend = user + record.BackendUser = user } return true, &userID, &sessionID, user, nil @@ -419,8 +435,8 @@ func (b *LibreGraphIdentifierBackend) Logon(ctx context.Context, audience, usern func (b *LibreGraphIdentifierBackend) GetUser(ctx context.Context, entryID string, sessionRef *string, requestedScopes map[string]bool) (backends.UserFromBackend, error) { record, _ := identifier.FromRecordContext(ctx) if record != nil { - if record.UserFromBackend != nil { - if user, ok := record.UserFromBackend.(*libreGraphUser); ok { + if record.BackendUser != nil { + if user, ok := record.BackendUser.(*libreGraphUser); ok { // Fastpath, if logon previously injected the user. if user.ID == entryID { return user, nil @@ -455,6 +471,23 @@ func (b *LibreGraphIdentifierBackend) GetUser(ctx context.Context, entryID strin req.Header.Set("X-SessionID", sessionID) } } + + if record == nil { + record, _ = identifier.RecordFromRequestContext(ctx, b.config) + } + if record != nil { + if record.IdentifiedUser != nil { + if ok, ts := record.IdentifiedUser.LoggedOn(); ok { + req.Header.Set("X-User-Logon-At", ts.UTC().Format(http.TimeFormat)) + } + } + if record.RealIP != "" { + req.Header.Set("X-User-Real-IP", record.RealIP) + } + if record.UserAgent != "" { + req.Header.Set("X-User-Real-User-Agent", record.UserAgent) + } + } req.Header.Set("User-Agent", utils.DefaultHTTPUserAgent) // Inject select parameter. @@ -489,7 +522,7 @@ func (b *LibreGraphIdentifierBackend) GetUser(ctx context.Context, entryID strin return user, nil } -// ResolveUserByUsername implements the Beckend interface, providing lookup for +// ResolveUserByUsername implements the Backend interface, providing lookup for // user by providing the username. Requests are bound to the provided context. func (b *LibreGraphIdentifierBackend) ResolveUserByUsername(ctx context.Context, username string) (backends.UserFromBackend, error) { // Libregraph backend accept both user name and ID lookups, so this is @@ -499,11 +532,72 @@ func (b *LibreGraphIdentifierBackend) ResolveUserByUsername(ctx context.Context, // RefreshSession implements the Backend interface. func (b *LibreGraphIdentifierBackend) RefreshSession(ctx context.Context, userID string, sessionRef *string, claims map[string]interface{}) error { + user, err := b.GetUser(ctx, userID, sessionRef, nil) + if err != nil { + return err + } + if user == nil { + return fmt.Errorf("refresh session did not yield a user") + } return nil } -// DestroySession implements the Backend interface providing destroy to KC session. +// DestroySession implements the Backend interface providing explicit revoke +// of the backend session. func (b *LibreGraphIdentifierBackend) DestroySession(ctx context.Context, sessionRef *string) error { + var requestedScopes map[string]bool + record, _ := identifier.FromRecordContext(ctx) + if record != nil { + if record.HelloRequest != nil { + requestedScopes = record.HelloRequest.Scopes + } + } + + _, revokeSessionURL := b.getRevokeSigninSessionURL(requestedScopes) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, revokeSessionURL, http.NoBody) + if err != nil { + return fmt.Errorf("libregraph identifier backend destroy session request error: %w", err) + } + + if sessionRef != nil { + sessionID := *sessionRef + if !strings.HasPrefix(sessionID, libreGraphIdentifierBackendName+":") { + // Only send the session ID if it is not a ref generated by lico. + req.Header.Set("X-SessionID", sessionID) + } + } + + if record == nil { + record, _ = identifier.RecordFromRequestContext(ctx, b.config) + } + if record != nil { + // Inject HTTP headers. + if record.RealIP != "" { + req.Header.Set("X-User-Real-IP", record.RealIP) + } + if record.UserAgent != "" { + req.Header.Set("X-User-Real-User-Agent", record.UserAgent) + } + } + req.Header.Set("User-Agent", utils.DefaultHTTPUserAgent) + + response, err := b.client.Do(req) + if err != nil { + return fmt.Errorf("libregraph identifier backend destroy session request failed: %w", err) + } + defer response.Body.Close() + + switch response.StatusCode { + case http.StatusOK: + // breaks + case http.StatusNotFound: + return nil + case http.StatusUnauthorized: + return nil + default: + return fmt.Errorf("libregraph identifier backend logon request unexpected response status: %d", response.StatusCode) + } + return nil } @@ -558,3 +652,9 @@ func (b *LibreGraphIdentifierBackend) getUserURL(requestedScopes map[string]bool return scope, baseURL + apiPathUsers } + +func (b *LibreGraphIdentifierBackend) getRevokeSigninSessionURL(requestedScopes map[string]bool) (string, string) { + scope, baseURL := b.getBaseURL(requestedScopes) + + return scope, baseURL + apiPathRevokeSignInSession +} diff --git a/vendor/github.com/libregraph/lico/identifier/context.go b/vendor/github.com/libregraph/lico/identifier/context.go index 0fca3ed24d..0bcc68f7a7 100644 --- a/vendor/github.com/libregraph/lico/identifier/context.go +++ b/vendor/github.com/libregraph/lico/identifier/context.go @@ -19,26 +19,56 @@ package identifier import ( "context" + "net" + "net/http" + konnect "github.com/libregraph/lico" + "github.com/libregraph/lico/config" "github.com/libregraph/lico/identifier/backends" + "github.com/libregraph/lico/utils" ) // Record is the struct which the identifier puts into the context. type Record struct { - HelloRequest *HelloRequest - UserFromBackend backends.UserFromBackend + HelloRequest *HelloRequest + RealIP string + UserAgent string + + BackendUser backends.UserFromBackend + IdentifiedUser *IdentifiedUser +} + +func NewRecord(req *http.Request, c *config.Config) *Record { + record := &Record{ + UserAgent: req.UserAgent(), + } + + trusted, _ := utils.IsRequestFromTrustedSource(req, c.TrustedProxyIPs, c.TrustedProxyNets) + + if trusted { + record.RealIP = req.Header.Get("X-Real-Ip") + } + if record.RealIP == "" { + if ip, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { + record.RealIP = ip + } + } + + return record } // key is an unexported type for keys defined in this package. // This prevents collisions with keys defined in other packages. type key int -// recordKey is the key for identifier.Record in Contexts. It is -// unexported; clients use identifier.NewContext and identifier.FromContext -// instead of using this key directly. -var recordKey key +// Keys for context data. +// Unexported; Clients use identifier.New{?}Context and +// identifier.From{?}Context functions instead of using these keys directly. +const ( + recordKey key = iota +) -// NewRecordContext returns a new Context that carries value HelloRequest. +// NewRecordContext returns a new Context that carries the Record. func NewRecordContext(ctx context.Context, record *Record) context.Context { return context.WithValue(ctx, recordKey, record) } @@ -48,3 +78,12 @@ func FromRecordContext(ctx context.Context) (*Record, bool) { record, ok := ctx.Value(recordKey).(*Record) return record, ok } + +// RecordFromRequestContext returns a new Record value based on the request +// stored in ctx, if any. +func RecordFromRequestContext(ctx context.Context, c *config.Config) (*Record, bool) { + if req, ok := konnect.FromRequestContext(ctx); ok { + return NewRecord(req, c), true + } + return nil, false +} diff --git a/vendor/github.com/libregraph/lico/identifier/handlers.go b/vendor/github.com/libregraph/lico/identifier/handlers.go index a326d8e36e..d637ab2b26 100644 --- a/vendor/github.com/libregraph/lico/identifier/handlers.go +++ b/vendor/github.com/libregraph/lico/identifier/handlers.go @@ -21,6 +21,7 @@ import ( "encoding/json" "encoding/xml" "fmt" + "net" "net/http" "net/url" "strings" @@ -28,6 +29,7 @@ import ( "github.com/sirupsen/logrus" + konnect "github.com/libregraph/lico" "github.com/libregraph/lico/identity/authorities" "github.com/libregraph/lico/utils" ) @@ -57,6 +59,14 @@ func (i *Identifier) secureHandler(handler http.Handler) http.Handler { // NOTE: this does not protect from DNS rebinding. Protection for that // should be added at the frontend proxy. requiredHost := req.Host + if host, port, splitErr := net.SplitHostPort(requiredHost); splitErr == nil { + if port == "443" { + // Ignore the port 443 as it is the default port and it is + // usually not part of any of the urls. It might be in the + // request for HTTP/3 requests. + requiredHost = host + } + } // This follows https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet for { @@ -172,7 +182,7 @@ func (i *Identifier) handleLogon(rw http.ResponseWriter, req *http.Request) { addNoCacheResponseHeaders(rw.Header()) - record := &Record{} + record := NewRecord(req, i.Config.Config) if r.Hello != nil { err = r.Hello.parse() @@ -184,7 +194,7 @@ func (i *Identifier) handleLogon(rw http.ResponseWriter, req *http.Request) { record.HelloRequest = r.Hello } - req = req.WithContext(NewRecordContext(req.Context(), record)) + req = req.WithContext(NewRecordContext(konnect.NewRequestContext(req.Context(), req), record)) // Params is an array like this [$username, $password, $mode], defining a // extensible way to extend login modes over time. The minimal length of diff --git a/vendor/github.com/libregraph/lico/identifier/package.json b/vendor/github.com/libregraph/lico/identifier/package.json index 8ad257719b..8f72418e38 100644 --- a/vendor/github.com/libregraph/lico/identifier/package.json +++ b/vendor/github.com/libregraph/lico/identifier/package.json @@ -33,7 +33,7 @@ "serve": "vite preview", "test": "vitest", "lint": "eslint --max-warnings=0 src/**/*.{ts,tsx,js,jsx}", - "licenses": "NODE_PATH=./node_modules node ../scripts/js-license-ranger.js", + "licenses": "node ../scripts/js-license-ranger.mjs", "analyze": "source-map-explorer 'build/static/assets/*.js'" }, "devDependencies": { @@ -59,7 +59,7 @@ "i18next-parser": "^5.4.0", "if-node-version": "^1.1.1", "jsdom": "^22.1.0", - "source-map-explorer": "^1.8.0", + "source-map-explorer": "^2.5.3", "typescript": "^5.2.2", "vite": "^4.5.2", "vite-plugin-checker": "^0.6.2", diff --git a/vendor/github.com/libregraph/lico/identity/context.go b/vendor/github.com/libregraph/lico/identity/context.go index a3dc2b1070..13b6b0e938 100644 --- a/vendor/github.com/libregraph/lico/identity/context.go +++ b/vendor/github.com/libregraph/lico/identity/context.go @@ -28,7 +28,9 @@ type key int // authRecordKey is the key for identity.AuthRecord in Contexts. It is // unexported; clients use identity.NewContext and identity.FromContext // instead of using this key directly. -var authRecordKey key +const ( + authRecordKey key = iota +) // NewContext returns a new Context that carries value auth. func NewContext(ctx context.Context, auth AuthRecord) context.Context { diff --git a/vendor/github.com/libregraph/lico/identity/managers/identifier.go b/vendor/github.com/libregraph/lico/identity/managers/identifier.go index e01e53de5f..c832b38c63 100644 --- a/vendor/github.com/libregraph/lico/identity/managers/identifier.go +++ b/vendor/github.com/libregraph/lico/identity/managers/identifier.go @@ -200,6 +200,10 @@ func (im *IdentifierIdentityManager) Authenticate(ctx context.Context, rw http.R } if user != nil { + record := identifier.NewRecord(req, im.identifier.Config.Config) + record.IdentifiedUser = user.IdentifiedUser + ctx = identifier.NewRecordContext(ctx, record) + // Inject required scopes into request. for scope, ok := range user.RequiredScopes() { ar.Scopes[scope] = ok diff --git a/vendor/github.com/libregraph/lico/oidc/payload/authentication.go b/vendor/github.com/libregraph/lico/oidc/payload/authentication.go index 6348406a77..2080b5fe8e 100644 --- a/vendor/github.com/libregraph/lico/oidc/payload/authentication.go +++ b/vendor/github.com/libregraph/lico/oidc/payload/authentication.go @@ -265,28 +265,36 @@ func (ar *AuthenticationRequest) ApplyRequestObject(roc *RequestObjectClaims, me // Validate validates the request data of the accociated authentication request. func (ar *AuthenticationRequest) Validate(keyFunc jwt.Keyfunc) error { - if _, ok := ar.Scopes[oidc.ScopeOpenID]; !ok { - return ar.NewBadRequest(oidc.ErrorCodeOAuth2InvalidRequest, "missing openid scope in request") - } - switch ar.RawResponseType { case oidc.ResponseTypeCode: // Code flow. // breaks case oidc.ResponseTypeCodeIDToken: // Hybgrid flow. + if _, ok := ar.Scopes[oidc.ScopeOpenID]; !ok { + return ar.NewBadRequest(oidc.ErrorCodeOAuth2InvalidRequest, "missing openid scope in request") + } // breaks case oidc.ResponseTypeCodeToken: // Hybgrid flow. // breaks case oidc.ResponseTypeCodeIDTokenToken: // Hybgrid flow. + if _, ok := ar.Scopes[oidc.ScopeOpenID]; !ok { + return ar.NewBadRequest(oidc.ErrorCodeOAuth2InvalidRequest, "missing openid scope in request") + } // breaks case oidc.ResponseTypeIDToken: // Implicit flow. + if _, ok := ar.Scopes[oidc.ScopeOpenID]; !ok { + return ar.NewBadRequest(oidc.ErrorCodeOAuth2InvalidRequest, "missing openid scope in request") + } fallthrough case oidc.ResponseTypeIDTokenToken: // Implicit flow with access token. + if _, ok := ar.Scopes[oidc.ScopeOpenID]; !ok { + return ar.NewBadRequest(oidc.ErrorCodeOAuth2InvalidRequest, "missing openid scope in request") + } if ar.Nonce == "" { return ar.NewError(oidc.ErrorCodeOAuth2InvalidRequest, "nonce is required for implicit flow") } diff --git a/vendor/github.com/libregraph/lico/oidc/provider/handlers.go b/vendor/github.com/libregraph/lico/oidc/provider/handlers.go index 3cc4621314..78e046b3fd 100644 --- a/vendor/github.com/libregraph/lico/oidc/provider/handlers.go +++ b/vendor/github.com/libregraph/lico/oidc/provider/handlers.go @@ -93,6 +93,8 @@ func (p *Provider) AuthorizeHandler(rw http.ResponseWriter, req *http.Request) { addResponseHeaders(rw.Header()) + ctx := konnect.NewRequestContext(req.Context(), req) + // OpenID Connect 1.0 authentication request validation. // http://openid.net/specs/openid-connect-core-1_0.html#ImplicitValidation err = req.ParseForm() @@ -106,7 +108,7 @@ func (p *Provider) AuthorizeHandler(rw http.ResponseWriter, req *http.Request) { if claims, ok := token.Claims.(*payload.RequestObjectClaims); ok { // Validate signed request tokens according to spec defined at // https://openid.net/specs/openid-connect-core-1_0.html#SignedRequestObject - registration, _ := p.clients.Get(req.Context(), claims.ClientID) + registration, _ := p.clients.Get(ctx, claims.ClientID) if registration != nil { if registration.RawRequestObjectSigningAlg != "" { if token.Method.Alg() != registration.RawRequestObjectSigningAlg { @@ -156,7 +158,7 @@ func (p *Provider) AuthorizeHandler(rw http.ResponseWriter, req *http.Request) { } // Inject implicit scopes set by client registration. - if registration, _ := p.clients.Get(req.Context(), ar.ClientID); registration != nil { + if registration, _ := p.clients.Get(ctx, ar.ClientID); registration != nil { err = registration.ApplyImplicitScopes(ar.Scopes) if err != nil { p.logger.WithError(err).Debugln("failed to apply implicit scopes") @@ -171,7 +173,7 @@ func (p *Provider) AuthorizeHandler(rw http.ResponseWriter, req *http.Request) { // Authorization Server Authenticates End-User // http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthenticates - auth, err = p.identityManager.Authenticate(req.Context(), rw, req, ar, p.guestManager) + auth, err = p.identityManager.Authenticate(ctx, rw, req, ar, p.guestManager) if err != nil { goto done } @@ -190,7 +192,7 @@ func (p *Provider) AuthorizeHandler(rw http.ResponseWriter, req *http.Request) { // Authorization Server Obtains End-User Consent/Authorization // http://openid.net/specs/openid-connect-core-1_0.html#ImplicitConsent - auth, err = auth.Manager().Authorize(req.Context(), rw, req, ar, auth) + auth, err = auth.Manager().Authorize(ctx, rw, req, ar, auth) if err != nil { goto done } @@ -213,7 +215,7 @@ func (p *Provider) AuthorizeResponse(rw http.ResponseWriter, req *http.Request, goto done } - ctx = identity.NewContext(req.Context(), auth) + ctx = identity.NewContext(konnect.NewRequestContext(req.Context(), req), auth) // Create session. session, err = p.updateOrCreateSession(rw, req, ar, auth) @@ -336,6 +338,8 @@ func (p *Provider) TokenHandler(rw http.ResponseWriter, req *http.Request) { rw.Header().Set("Cache-Control", "no-store") rw.Header().Set("Pragma", "no-cache") + ctx := konnect.NewRequestContext(req.Context(), req) + // Validate request method switch req.Method { case http.MethodPost: @@ -367,7 +371,7 @@ func (p *Provider) TokenHandler(rw http.ResponseWriter, req *http.Request) { } // Additional validations according to https://tools.ietf.org/html/rfc6749#section-4.1.3 - clientDetails, err = p.clients.Lookup(req.Context(), tr.ClientID, tr.ClientSecret, tr.RedirectURI, "", false) + clientDetails, err = p.clients.Lookup(ctx, tr.ClientID, tr.ClientSecret, tr.RedirectURI, "", false) if err != nil { err = konnectoidc.NewOAuth2Error(oidc.ErrorCodeOAuth2AccessDenied, err.Error()) goto done @@ -410,8 +414,8 @@ func (p *Provider) TokenHandler(rw http.ResponseWriter, req *http.Request) { } } - if _, ok := identity.FromContext(req.Context()); !ok { - req = req.WithContext(identity.NewContext(req.Context(), auth)) + if _, ok := identity.FromContext(ctx); !ok { + ctx = identity.NewContext(ctx, auth) } case oidc.GrantTypeRefreshToken: @@ -437,10 +441,11 @@ func (p *Provider) TokenHandler(rw http.ResponseWriter, req *http.Request) { goto done } - ctx := konnect.NewClaimsContext(req.Context(), claims) + ctx = konnect.NewClaimsContext(ctx, claims) - currentIdentityManager, err := p.getIdentityManagerFromClaims(claims.IdentityProvider, claims.IdentityClaims) - if err != nil { + currentIdentityManager, claimsErr := p.getIdentityManagerFromClaims(claims.IdentityProvider, claims.IdentityClaims) + if claimsErr != nil { + err = claimsErr goto done } @@ -499,16 +504,16 @@ func (p *Provider) TokenHandler(rw http.ResponseWriter, req *http.Request) { } // Create access token. - accessTokenString, err = p.makeAccessToken(req.Context(), ar.ClientID, auth, signinMethod) + accessTokenString, err = p.makeAccessToken(ctx, ar.ClientID, auth, signinMethod) if err != nil { goto done } switch tr.GrantType { case oidc.GrantTypeAuthorizationCode: - // Create ID token when not previously requested. - if !ar.ResponseTypes[oidc.ResponseTypeIDToken] { - idTokenString, err = p.makeIDToken(req.Context(), ar, auth, session, accessTokenString, "", signinMethod) + // Create ID token when not previously requested amd openid scope is authorized. + if !ar.ResponseTypes[oidc.ResponseTypeIDToken] && authorizedScopes[oidc.ScopeOpenID] { + idTokenString, err = p.makeIDToken(ctx, ar, auth, session, accessTokenString, "", signinMethod) if err != nil { goto done } @@ -516,7 +521,7 @@ func (p *Provider) TokenHandler(rw http.ResponseWriter, req *http.Request) { // Create refresh token when granted. if authorizedScopes[oidc.ScopeOfflineAccess] { - refreshTokenString, err = p.makeRefreshToken(req.Context(), ar.ClientID, auth, nil) + refreshTokenString, err = p.makeRefreshToken(ctx, ar.ClientID, auth, nil) if err != nil { goto done } @@ -595,7 +600,7 @@ func (p *Provider) UserInfoHandler(rw http.ResponseWriter, req *http.Request) { userID, sessionRef := p.getUserIDAndSessionRefFromClaims(&claims.StandardClaims, claims.SessionClaims, claims.IdentityClaims) - ctx := konnect.NewClaimsContext(req.Context(), claims) + ctx := konnect.NewClaimsContext(konnect.NewRequestContext(req.Context(), req), claims) currentIdentityManager, err := p.getIdentityManagerFromClaims(claims.IdentityProvider, claims.IdentityClaims) if err != nil { @@ -712,7 +717,7 @@ done: // Support returning signed user info if the registered client requested it // as specified in https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse and // https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata - registration, _ := p.clients.Get(req.Context(), claims.Audience) + registration, _ := p.clients.Get(ctx, claims.Audience) if registration != nil { if registration.RawUserInfoSignedResponseAlg != "" { // Get alg. @@ -720,7 +725,7 @@ done: // Set extra claims. responseAsMap[oidc.IssuerIdentifierClaim] = p.issuerIdentifier responseAsMap[oidc.AudienceClaim] = registration.ID - tokenString, err := p.makeJWT(req.Context(), alg, jwt.MapClaims(responseAsMap)) + tokenString, err := p.makeJWT(ctx, alg, jwt.MapClaims(responseAsMap)) if err != nil { p.logger.WithFields(utils.ErrorAsFields(err)).Debugln("userinfo request failed to encode jwt") p.ErrorPage(rw, http.StatusInternalServerError, "", err.Error()) @@ -749,6 +754,8 @@ func (p *Provider) EndSessionHandler(rw http.ResponseWriter, req *http.Request) addResponseHeaders(rw.Header()) + ctx := konnect.NewRequestContext(req.Context(), req) + // Validate request. err = req.ParseForm() if err != nil { @@ -783,7 +790,7 @@ func (p *Provider) EndSessionHandler(rw http.ResponseWriter, req *http.Request) } // Authorization unauthenticates end user. - err = currentIdentityManager.EndSession(req.Context(), rw, req, esr) + err = currentIdentityManager.EndSession(ctx, rw, req, esr) if err != nil { goto done } diff --git a/vendor/github.com/rs/cors/cors.go b/vendor/github.com/rs/cors/cors.go index da80d343b3..724f242ac6 100644 --- a/vendor/github.com/rs/cors/cors.go +++ b/vendor/github.com/rs/cors/cors.go @@ -364,9 +364,11 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { // Note: the Fetch standard guarantees that at most one // Access-Control-Request-Headers header is present in the preflight request; // see step 5.2 in https://fetch.spec.whatwg.org/#cors-preflight-fetch-0. - reqHeaders, found := first(r.Header, "Access-Control-Request-Headers") - if found && !c.allowedHeadersAll && !c.allowedHeaders.Subsumes(reqHeaders[0]) { - c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders[0]) + // However, some gateways split that header into multiple headers of the same name; + // see https://github.com/rs/cors/issues/184. + reqHeaders, found := r.Header["Access-Control-Request-Headers"] + if found && !c.allowedHeadersAll && !c.allowedHeaders.Accepts(reqHeaders) { + c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders) return } if c.allowedOriginsAll { @@ -391,9 +393,7 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { if len(c.maxAge) > 0 { headers["Access-Control-Max-Age"] = c.maxAge } - if c.Log != nil { - c.logf(" Preflight response headers: %v", headers) - } + c.logf(" Preflight response headers: %v", headers) } // handleActualRequest handles simple cross-origin requests, actual request or redirects @@ -440,9 +440,7 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { if c.allowCredentials { headers["Access-Control-Allow-Credentials"] = headerTrue } - if c.Log != nil { - c.logf(" Actual response added headers: %v", headers) - } + c.logf(" Actual response added headers: %v", headers) } // convenience method. checks if a logger is set. diff --git a/vendor/github.com/rs/cors/internal/sortedset.go b/vendor/github.com/rs/cors/internal/sortedset.go index 513da20f7d..844f3f9e03 100644 --- a/vendor/github.com/rs/cors/internal/sortedset.go +++ b/vendor/github.com/rs/cors/internal/sortedset.go @@ -52,48 +52,136 @@ func (set SortedSet) String() string { return strings.Join(elems, ",") } -// Subsumes reports whether csv is a sequence of comma-separated names that are -// - all elements of set, -// - sorted in lexicographically order, +// Accepts reports whether values is a sequence of list-based field values +// whose elements are +// - all members of set, +// - sorted in lexicographical order, // - unique. -func (set SortedSet) Subsumes(csv string) bool { - if csv == "" { - return true - } - posOfLastNameSeen := -1 - chunkSize := set.maxLen + 1 // (to accommodate for at least one comma) - for { - // As a defense against maliciously long names in csv, - // we only process at most chunkSize bytes per iteration. - end := min(len(csv), chunkSize) - comma := strings.IndexByte(csv[:end], ',') - var name string - if comma == -1 { - name = csv - } else { - name = csv[:comma] +func (set SortedSet) Accepts(values []string) bool { + var ( // effectively constant + maxLen = maxOWSBytes + set.maxLen + maxOWSBytes + 1 // +1 for comma + ) + var ( + posOfLastNameSeen = -1 + name string + commaFound bool + emptyElements int + ok bool + ) + for _, s := range values { + for { + // As a defense against maliciously long names in s, + // we process only a small number of s's leading bytes per iteration. + name, s, commaFound = cutAtComma(s, maxLen) + name, ok = trimOWS(name, maxOWSBytes) + if !ok { + return false + } + if name == "" { + // RFC 9110 requires recipients to tolerate + // "a reasonable number of empty list elements"; see + // https://httpwg.org/specs/rfc9110.html#abnf.extension.recipient. + emptyElements++ + if emptyElements > maxEmptyElements { + return false + } + if !commaFound { // We have now exhausted the names in s. + break + } + continue + } + pos, ok := set.m[name] + if !ok { + return false + } + // The names in s are expected to be sorted in lexicographical order + // and to each appear at most once. + // Therefore, the positions (in set) of the names that + // appear in s should form a strictly increasing sequence. + // If that's not actually the case, bail out. + if pos <= posOfLastNameSeen { + return false + } + posOfLastNameSeen = pos + if !commaFound { // We have now exhausted the names in s. + break + } } - pos, found := set.m[name] - if !found { - return false - } - // The names in csv are expected to be sorted in lexicographical order - // and appear at most once in csv. - // Therefore, the positions (in set) of the names that - // appear in csv should form a strictly increasing sequence. - // If that's not actually the case, bail out. - if pos <= posOfLastNameSeen { - return false - } - posOfLastNameSeen = pos - if comma < 0 { // We've now processed all the names in csv. - break - } - csv = csv[comma+1:] } return true } +const ( + maxOWSBytes = 1 // number of leading/trailing OWS bytes tolerated + maxEmptyElements = 16 // number of empty list elements tolerated +) + +func cutAtComma(s string, n int) (before, after string, found bool) { + // Note: this implementation draws inspiration from strings.Cut's. + end := min(len(s), n) + if i := strings.IndexByte(s[:end], ','); i >= 0 { + after = s[i+1:] // deal with this first to save one bounds check + return s[:i], after, true + } + return s, "", false +} + +// TrimOWS trims up to n bytes of [optional whitespace (OWS)] +// from the start of and/or the end of s. +// If no more than n bytes of OWS are found at the start of s +// and no more than n bytes of OWS are found at the end of s, +// it returns the trimmed result and true. +// Otherwise, it returns the original string and false. +// +// [optional whitespace (OWS)]: https://httpwg.org/specs/rfc9110.html#whitespace +func trimOWS(s string, n int) (trimmed string, ok bool) { + if s == "" { + return s, true + } + trimmed, ok = trimRightOWS(s, n) + if !ok { + return s, false + } + trimmed, ok = trimLeftOWS(trimmed, n) + if !ok { + return s, false + } + return trimmed, true +} + +func trimLeftOWS(s string, n int) (string, bool) { + sCopy := s + var i int + for len(s) > 0 { + if i > n { + return sCopy, false + } + if !(s[0] == ' ' || s[0] == '\t') { + break + } + s = s[1:] + i++ + } + return s, true +} + +func trimRightOWS(s string, n int) (string, bool) { + sCopy := s + var i int + for len(s) > 0 { + if i > n { + return sCopy, false + } + last := len(s) - 1 + if !(s[last] == ' ' || s[last] == '\t') { + break + } + s = s[:last] + i++ + } + return s, true +} + // TODO: when updating go directive to 1.21 or later, // use min builtin instead. func min(a, b int) int { diff --git a/vendor/github.com/rs/cors/utils.go b/vendor/github.com/rs/cors/utils.go index 7019f45cd9..41b0c2836a 100644 --- a/vendor/github.com/rs/cors/utils.go +++ b/vendor/github.com/rs/cors/utils.go @@ -1,7 +1,6 @@ package cors import ( - "net/http" "strings" ) @@ -24,11 +23,3 @@ func convert(s []string, f func(string) string) []string { } return out } - -func first(hdrs http.Header, k string) ([]string, bool) { - v, found := hdrs[k] - if !found || len(v) == 0 { - return nil, false - } - return v[:1], true -} diff --git a/vendor/modules.txt b/vendor/modules.txt index c652a52940..9ab21616c4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1300,7 +1300,7 @@ github.com/libregraph/idm/server github.com/libregraph/idm/server/handler github.com/libregraph/idm/server/handler/boltdb github.com/libregraph/idm/server/handler/ldif -# github.com/libregraph/lico v0.62.0 +# github.com/libregraph/lico v0.64.0 ## explicit; go 1.18 github.com/libregraph/lico github.com/libregraph/lico/bootstrap @@ -1723,7 +1723,7 @@ github.com/rogpeppe/go-internal/internal/syscall/windows/sysdll github.com/rogpeppe/go-internal/lockedfile github.com/rogpeppe/go-internal/lockedfile/internal/filelock github.com/rogpeppe/go-internal/semver -# github.com/rs/cors v1.11.0 +# github.com/rs/cors v1.11.1 ## explicit; go 1.13 github.com/rs/cors github.com/rs/cors/internal