From 333d1bfe7d1e4fb36693bb6ca9bfb669f153703a Mon Sep 17 00:00:00 2001 From: tan Date: Sat, 19 Oct 2024 06:03:54 +0530 Subject: [PATCH] [Dex] Update Dex to v2.41.1 Updates Dex to v2.41.1. Also updates the patch made earlier to oidc plugin, to remove parts of the changes that got upstreamed via https://github.com/dexidp/dex/pull/3074. Retains (with updates to match source), the other patches made earlier to support additional scopes for github adn gitlab logins and to enable multiple sessions. --- D/Dex/build_tarballs.jl | 12 +- ...-allow-optional-github-gitlab-scopes.patch | 103 ++++---- .../02-allow-multiple-refresh-tokens.patch | 220 ++++++++++-------- .../patches/03-group-support-oidc.patch | 140 +---------- 4 files changed, 191 insertions(+), 284 deletions(-) diff --git a/D/Dex/build_tarballs.jl b/D/Dex/build_tarballs.jl index 4d4168ffe74..4121a553891 100644 --- a/D/Dex/build_tarballs.jl +++ b/D/Dex/build_tarballs.jl @@ -3,11 +3,11 @@ using BinaryBuilder, Pkg name = "Dex" -version = v"2.30.2" +version = v"2.41.1" # Collection of sources required to complete build sources = [ - GitSource("https://github.com/dexidp/dex.git", "6e30b362b7238d5de80b8277bb47ece3994fec95"), + GitSource("https://github.com/dexidp/dex.git", "43956db7fd75c488a82c70cf231f44287300a75d"), DirectorySource("bundled"), ] @@ -21,7 +21,9 @@ for f in ${WORKSPACE}/srcdir/patches/*.patch; do done install_license LICENSE go mod tidy -go mod download entgo.io/ent +go get -u entgo.io/contrib/entproto@latest +go get entgo.io/contrib/entproto/cmd/protoc-gen-entgrpc@latest +go mod tidy make build mkdir -p $bindir mv bin/dex "$bindir/dex${exeext}" @@ -31,8 +33,8 @@ tar -czvf $prefix/share/webtemplates.tar.gz -C ./web static templates themes # These are the platforms we will build for by default, unless further # platforms are passed in on the command line platforms = [ - Platform("x86_64", "linux"; libc="musl"), - Platform("x86_64", "linux"; libc="glibc") + Platform("x86_64", "linux"; libc="glibc"), + Platform("x86_64", "linux"; libc="musl") ] diff --git a/D/Dex/bundled/patches/01-allow-optional-github-gitlab-scopes.patch b/D/Dex/bundled/patches/01-allow-optional-github-gitlab-scopes.patch index 812c09c97f5..7f95f0e43c9 100644 --- a/D/Dex/bundled/patches/01-allow-optional-github-gitlab-scopes.patch +++ b/D/Dex/bundled/patches/01-allow-optional-github-gitlab-scopes.patch @@ -1,68 +1,70 @@ diff --git a/connector/github/github.go b/connector/github/github.go -index 02f2cae8..e3e9b9c3 100644 +index 18a56628..9bbe1e2c 100644 --- a/connector/github/github.go +++ b/connector/github/github.go -@@ -42,16 +42,17 @@ var ( +@@ -39,17 +39,18 @@ var ( // Config holds configuration options for github logins. type Config struct { -- ClientID string `json:"clientID"` -- ClientSecret string `json:"clientSecret"` -- RedirectURI string `json:"redirectURI"` -- Org string `json:"org"` -- Orgs []Org `json:"orgs"` -- HostName string `json:"hostName"` -- RootCA string `json:"rootCA"` -- TeamNameField string `json:"teamNameField"` -- LoadAllGroups bool `json:"loadAllGroups"` -- UseLoginAsID bool `json:"useLoginAsID"` -+ ClientID string `json:"clientID"` -+ ClientSecret string `json:"clientSecret"` -+ RedirectURI string `json:"redirectURI"` -+ Org string `json:"org"` -+ Orgs []Org `json:"orgs"` -+ HostName string `json:"hostName"` -+ RootCA string `json:"rootCA"` -+ TeamNameField string `json:"teamNameField"` -+ LoadAllGroups bool `json:"loadAllGroups"` -+ UseLoginAsID bool `json:"useLoginAsID"` -+ AdditionalScopes []string `json:"additionalScopes,omitempty"` +- ClientID string `json:"clientID"` +- ClientSecret string `json:"clientSecret"` +- RedirectURI string `json:"redirectURI"` +- Org string `json:"org"` +- Orgs []Org `json:"orgs"` +- HostName string `json:"hostName"` +- RootCA string `json:"rootCA"` +- TeamNameField string `json:"teamNameField"` +- LoadAllGroups bool `json:"loadAllGroups"` +- UseLoginAsID bool `json:"useLoginAsID"` +- PreferredEmailDomain string `json:"preferredEmailDomain"` ++ ClientID string `json:"clientID"` ++ ClientSecret string `json:"clientSecret"` ++ RedirectURI string `json:"redirectURI"` ++ Org string `json:"org"` ++ Orgs []Org `json:"orgs"` ++ HostName string `json:"hostName"` ++ RootCA string `json:"rootCA"` ++ TeamNameField string `json:"teamNameField"` ++ LoadAllGroups bool `json:"loadAllGroups"` ++ UseLoginAsID bool `json:"useLoginAsID"` ++ PreferredEmailDomain string `json:"preferredEmailDomain"` ++ AdditionalScopes []string `json:"additionalScopes"` } // Org holds org-team filters, in which teams are optional. -@@ -86,6 +87,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) - apiURL: apiURL, - logger: logger, - useLoginAsID: c.UseLoginAsID, -+ additionalScopes: c.AdditionalScopes, +@@ -85,6 +86,7 @@ func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, erro + logger: logger.With(slog.Group("connector", "type", "github", "id", id)), + useLoginAsID: c.UseLoginAsID, + preferredEmailDomain: c.PreferredEmailDomain, ++ additionalScopes: c.AdditionalScopes, } if c.HostName != "" { -@@ -152,6 +154,8 @@ type githubConnector struct { - loadAllGroups bool - // if set to true will use the user's handle rather than their numeric id as the ID +@@ -159,6 +161,8 @@ type githubConnector struct { useLoginAsID bool + // the domain to be preferred among the user's emails. e.g. "github.com" + preferredEmailDomain string + // optional scopes to be requested apart from what the connector itself needs + additionalScopes []string } // groupsRequired returns whether dex requires GitHub's 'read:org' scope. Dex -@@ -168,6 +172,10 @@ func (c *githubConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { +@@ -175,6 +179,10 @@ func (c *githubConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { if c.groupsRequired(scopes.Groups) { githubScopes = append(githubScopes, scopeOrgs) } + if len(c.additionalScopes) > 0 { -+ c.logger.Warnf("github: requesting additional scopes %v", c.additionalScopes) ++ c.logger.Warn(fmt.Sprintf("github: requesting additional scopes %v", c.additionalScopes)) + githubScopes = append(githubScopes, c.additionalScopes...) + } endpoint := github.Endpoint // case when it is a GitHub Enterprise account. diff --git a/connector/gitlab/gitlab.go b/connector/gitlab/gitlab.go -index e4060140..501f8b05 100644 +index fdb2c482..fb37b1c6 100644 --- a/connector/gitlab/gitlab.go +++ b/connector/gitlab/gitlab.go -@@ -27,12 +27,13 @@ const ( +@@ -28,12 +28,13 @@ const ( // Config holds configuration options for gitlab logins. type Config struct { @@ -82,15 +84,29 @@ index e4060140..501f8b05 100644 } type gitlabUser struct { -@@ -57,6 +58,7 @@ func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) - logger: logger, - groups: c.Groups, - useLoginAsID: c.UseLoginAsID, +@@ -51,13 +52,14 @@ func (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, erro + c.BaseURL = "https://gitlab.com" + } + return &gitlabConnector{ +- baseURL: c.BaseURL, +- redirectURI: c.RedirectURI, +- clientID: c.ClientID, +- clientSecret: c.ClientSecret, +- logger: logger.With(slog.Group("connector", "type", "gitlab", "id", id)), +- groups: c.Groups, +- useLoginAsID: c.UseLoginAsID, ++ baseURL: c.BaseURL, ++ redirectURI: c.RedirectURI, ++ clientID: c.ClientID, ++ clientSecret: c.ClientSecret, ++ logger: logger.With(slog.Group("connector", "type", "gitlab", "id", id)), ++ groups: c.Groups, ++ useLoginAsID: c.UseLoginAsID, + additionalScopes: c.AdditionalScopes, }, nil } -@@ -80,6 +82,8 @@ type gitlabConnector struct { +@@ -82,6 +84,8 @@ type gitlabConnector struct { httpClient *http.Client // if set to true will use the user's handle rather than their numeric id as the ID useLoginAsID bool @@ -99,14 +115,15 @@ index e4060140..501f8b05 100644 } func (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { -@@ -87,6 +91,10 @@ func (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { +@@ -89,7 +93,10 @@ func (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config { if c.groupsRequired(scopes.Groups) { gitlabScopes = []string{scopeUser, scopeOpenID} } +- + if len(c.additionalScopes) > 0 { -+ c.logger.Warnf("gitlab: requesting additional scopes %v", c.additionalScopes) ++ c.logger.Warn(fmt.Sprintf("gitlab: requesting additional scopes %v", c.additionalScopes)) + gitlabScopes = append(gitlabScopes, c.additionalScopes...) + } - gitlabEndpoint := oauth2.Endpoint{AuthURL: c.baseURL + "/oauth/authorize", TokenURL: c.baseURL + "/oauth/token"} return &oauth2.Config{ + ClientID: c.clientID, diff --git a/D/Dex/bundled/patches/02-allow-multiple-refresh-tokens.patch b/D/Dex/bundled/patches/02-allow-multiple-refresh-tokens.patch index 8592de6a823..16140d5c779 100644 --- a/D/Dex/bundled/patches/02-allow-multiple-refresh-tokens.patch +++ b/D/Dex/bundled/patches/02-allow-multiple-refresh-tokens.patch @@ -1,8 +1,8 @@ diff --git a/cmd/dex/config.go b/cmd/dex/config.go -index 309fc52c..8d8a7a7f 100644 +index dd6d2e2a..1d5bbab1 100644 --- a/cmd/dex/config.go +++ b/cmd/dex/config.go -@@ -49,6 +49,8 @@ type Config struct { +@@ -51,6 +51,8 @@ type Config struct { // querying the storage. Cannot be specified without enabling a passwords // database. StaticPasswords []password `json:"staticPasswords"` @@ -12,18 +12,21 @@ index 309fc52c..8d8a7a7f 100644 // Validate the configuration diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go -index 47030393..b66b3b2f 100644 +index 6fcca04d..998dc5fe 100644 --- a/cmd/dex/serve.go +++ b/cmd/dex/serve.go -@@ -260,18 +260,19 @@ func runServe(options serveOptions) error { +@@ -287,21 +287,22 @@ func runServe(options serveOptions) error { healthChecker := gosundheit.New() serverConfig := server.Config{ +- AllowedGrantTypes: c.OAuth2.GrantTypes, - SupportedResponseTypes: c.OAuth2.ResponseTypes, - SkipApprovalScreen: c.OAuth2.SkipApprovalScreen, - AlwaysShowLoginScreen: c.OAuth2.AlwaysShowLoginScreen, - PasswordConnector: c.OAuth2.PasswordConnector, +- Headers: c.Web.Headers.ToHTTPHeader(), - AllowedOrigins: c.Web.AllowedOrigins, +- AllowedHeaders: c.Web.AllowedHeaders, - Issuer: c.Issuer, - Storage: s, - Web: c.Frontend, @@ -31,11 +34,14 @@ index 47030393..b66b3b2f 100644 - Now: now, - PrometheusRegistry: prometheusRegistry, - HealthChecker: healthChecker, ++ AllowedGrantTypes: c.OAuth2.GrantTypes, + SupportedResponseTypes: c.OAuth2.ResponseTypes, + SkipApprovalScreen: c.OAuth2.SkipApprovalScreen, + AlwaysShowLoginScreen: c.OAuth2.AlwaysShowLoginScreen, + PasswordConnector: c.OAuth2.PasswordConnector, ++ Headers: c.Web.Headers.ToHTTPHeader(), + AllowedOrigins: c.Web.AllowedOrigins, ++ AllowedHeaders: c.Web.AllowedHeaders, + Issuer: c.Issuer, + Storage: s, + Web: c.Frontend, @@ -47,7 +53,7 @@ index 47030393..b66b3b2f 100644 } if c.Expiry.SigningKeys != "" { signingKeys, err := time.ParseDuration(c.Expiry.SigningKeys) -@@ -450,7 +451,7 @@ func runServe(options serveOptions) error { +@@ -508,7 +509,7 @@ func runServe(options serveOptions) error { } grpcSrv := grpc.NewServer(grpcOptions...) @@ -57,21 +63,21 @@ index 47030393..b66b3b2f 100644 grpcMetrics.InitializeMetrics(grpcSrv) if c.GRPC.Reflection { diff --git a/server/api.go b/server/api.go -index a68742b3..7a297bf7 100644 +index f53bc60b..86486169 100644 --- a/server/api.go +++ b/server/api.go -@@ -29,20 +29,22 @@ const ( +@@ -31,20 +31,22 @@ const ( ) // NewAPI returns a server which implements the gRPC API interface. --func NewAPI(s storage.Storage, logger log.Logger, version string) api.DexServer { -+func NewAPI(s storage.Storage, logger log.Logger, enableMultiRefreshTokens bool, version string) api.DexServer { +-func NewAPI(s storage.Storage, logger *slog.Logger, version string) api.DexServer { ++func NewAPI(s storage.Storage, logger *slog.Logger, enableMultiRefreshTokens bool, version string) api.DexServer { return dexAPI{ - s: s, -- logger: logger, +- logger: logger.With("component", "api"), - version: version, + s: s, -+ logger: logger, ++ logger: logger.With("component", "api"), + enableMultiRefreshTokens: enableMultiRefreshTokens, + version: version, } @@ -81,16 +87,16 @@ index a68742b3..7a297bf7 100644 api.UnimplementedDexServer - s storage.Storage -- logger log.Logger +- logger *slog.Logger - version string + s storage.Storage -+ logger log.Logger ++ logger *slog.Logger + enableMultiRefreshTokens bool + version string } - func (d dexAPI) CreateClient(ctx context.Context, req *api.CreateClientReq) (*api.CreateClientResp, error) { -@@ -283,6 +285,13 @@ func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) + func (d dexAPI) GetClient(ctx context.Context, req *api.GetClientReq) (*api.GetClientResp, error) { +@@ -304,6 +306,13 @@ func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) } func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.ListRefreshResp, error) { @@ -103,15 +109,15 @@ index a68742b3..7a297bf7 100644 +func (d dexAPI) listRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.ListRefreshResp, error) { id := new(internal.IDTokenSubject) if err := internal.Unmarshal(req.UserId, id); err != nil { - d.logger.Errorf("api: failed to unmarshal ID Token subject: %v", err) -@@ -316,7 +325,45 @@ func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api. + d.logger.Error("failed to unmarshal ID Token subject", "err", err) +@@ -337,7 +346,45 @@ func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api. }, nil } +func (d dexAPI) listRefreshMultiRefreshMode(ctx context.Context, req *api.ListRefreshReq) (*api.ListRefreshResp, error) { + id := new(internal.IDTokenSubject) + if err := internal.Unmarshal(req.UserId, id); err != nil { -+ d.logger.Errorf("api: failed to unmarshal ID Token subject: %v", err) ++ d.logger.Error(fmt.Sprintf("api: failed to unmarshal ID Token subject: %v", err)) + return nil, err + } + @@ -149,16 +155,15 @@ index a68742b3..7a297bf7 100644 +func (d dexAPI) revokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (*api.RevokeRefreshResp, error) { id := new(internal.IDTokenSubject) if err := internal.Unmarshal(req.UserId, id); err != nil { - d.logger.Errorf("api: failed to unmarshal ID Token subject: %v", err) -@@ -366,3 +413,42 @@ func (d dexAPI) RevokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (* - + d.logger.Error("failed to unmarshal ID Token subject", "err", err) +@@ -388,6 +435,45 @@ func (d dexAPI) RevokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (* return &api.RevokeRefreshResp{}, nil } -+ + +func (d dexAPI) revokeRefreshMultiRefreshMode(ctx context.Context, req *api.RevokeRefreshReq) (*api.RevokeRefreshResp, error) { + id := new(internal.IDTokenSubject) + if err := internal.Unmarshal(req.UserId, id); err != nil { -+ d.logger.Errorf("api: failed to unmarshal ID Token subject: %v", err) ++ d.logger.Error(fmt.Sprintf("api: failed to unmarshal ID Token subject: %v", err)) + return nil, err + } + @@ -174,7 +179,7 @@ index a68742b3..7a297bf7 100644 + for _, t := range refreshTokens { + if t.Claims.UserID == id.UserId && t.ConnectorID == id.ConnId && t.ClientID == req.ClientId { + if err := d.s.DeleteRefresh(t.ID); err != nil { -+ d.logger.Errorf("failed to delete refresh token: %v", err) ++ d.logger.Error(fmt.Sprintf("failed to delete refresh token: %v", err)) + return nil, err + } + } @@ -187,17 +192,21 @@ index a68742b3..7a297bf7 100644 + } + + if err := d.s.UpdateOfflineSessions(id.UserId, id.ConnId, updater); err != nil { -+ d.logger.Errorf("api: failed to update offline session object: %v", err) ++ d.logger.Error(fmt.Sprintf("api: failed to update offline session object: %v", err)) + return nil, err + } + + return &api.RevokeRefreshResp{}, nil +} ++ + func (d dexAPI) CreateConnector(ctx context.Context, req *api.CreateConnectorReq) (*api.CreateConnectorResp, error) { + if !featureflags.APIConnectorsCRUD.Enabled() { + return nil, fmt.Errorf("%s feature flag is not enabled", featureflags.APIConnectorsCRUD.Name) diff --git a/server/api_test.go b/server/api_test.go -index 02061340..7f627656 100644 +index bcf240c1..10ffe69f 100644 --- a/server/api_test.go +++ b/server/api_test.go -@@ -36,7 +36,7 @@ func newAPI(s storage.Storage, logger log.Logger, t *testing.T) *apiClient { +@@ -38,7 +38,7 @@ func newAPI(s storage.Storage, logger *slog.Logger, t *testing.T) *apiClient { } serv := grpc.NewServer() @@ -205,19 +214,19 @@ index 02061340..7f627656 100644 + api.RegisterDexServer(serv, NewAPI(s, logger, false, "test")) go serv.Serve(l) - // Dial will retry automatically if the serv.Serve() goroutine + // NewClient will retry automatically if the serv.Serve() goroutine diff --git a/server/handlers.go b/server/handlers.go -index 2a4f8c71..c8168368 100644 +index 63cb6122..0d14eb2a 100644 --- a/server/handlers.go +++ b/server/handlers.go -@@ -961,13 +961,15 @@ func (s *Server) exchangeAuthCode(w http.ResponseWriter, authCode storage.AuthCo +@@ -1057,13 +1057,15 @@ func (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, au return nil, err } } else { - if oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok { - // Delete old refresh token from storage. - if err := s.storage.DeleteRefresh(oldTokenRef.ID); err != nil && err != storage.ErrNotFound { -- s.logger.Errorf("failed to delete refresh token: %v", err) +- s.logger.ErrorContext(ctx, "failed to delete refresh token", "err", err) - s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) - deleteToken = true - return nil, err @@ -225,7 +234,7 @@ index 2a4f8c71..c8168368 100644 + if oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok { + // Delete old refresh token from storage. + if err := s.storage.DeleteRefresh(oldTokenRef.ID); err != nil && err != storage.ErrNotFound { -+ s.logger.Errorf("failed to delete refresh token: %v", err) ++ s.logger.Error("failed to delete refresh token", "err", err) + s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) + deleteToken = true + return nil, err @@ -233,7 +242,7 @@ index 2a4f8c71..c8168368 100644 } } -@@ -1205,16 +1207,18 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli +@@ -1310,16 +1312,18 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli return } } else { @@ -241,9 +250,9 @@ index 2a4f8c71..c8168368 100644 - // Delete old refresh token from storage. - if err := s.storage.DeleteRefresh(oldTokenRef.ID); err != nil { - if err == storage.ErrNotFound { -- s.logger.Warnf("database inconsistent, refresh token missing: %v", oldTokenRef.ID) +- s.logger.Warn("database inconsistent, refresh token missing", "token_id", oldTokenRef.ID) - } else { -- s.logger.Errorf("failed to delete refresh token: %v", err) +- s.logger.ErrorContext(r.Context(), "failed to delete refresh token", "err", err) - s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) - deleteToken = true - return @@ -252,9 +261,9 @@ index 2a4f8c71..c8168368 100644 + // Delete old refresh token from storage. + if err := s.storage.DeleteRefresh(oldTokenRef.ID); err != nil { + if err == storage.ErrNotFound { -+ s.logger.Warnf("database inconsistent, refresh token missing: %v", oldTokenRef.ID) ++ s.logger.Warn("database inconsistent, refresh token missing", "token_id", oldTokenRef.ID) + } else { -+ s.logger.Errorf("failed to delete refresh token: %v", err) ++ s.logger.Error("failed to delete refresh token", "err", err) + s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError) + deleteToken = true + return @@ -263,11 +272,11 @@ index 2a4f8c71..c8168368 100644 } } diff --git a/server/refreshhandlers.go b/server/refreshhandlers.go -index 8ea7ea9e..e8e9b67f 100644 +index 391d5522..4ce8b584 100644 --- a/server/refreshhandlers.go +++ b/server/refreshhandlers.go -@@ -197,7 +197,11 @@ func (s *Server) refreshWithConnector(ctx context.Context, token *internal.Refre - func (s *Server) updateOfflineSession(refresh *storage.RefreshToken, ident connector.Identity, lastUsed time.Time) *refreshError { +@@ -208,7 +208,11 @@ func (s *Server) refreshWithConnector(ctx context.Context, rCtx *refreshContext, + func (s *Server) updateOfflineSession(ctx context.Context, refresh *storage.RefreshToken, ident connector.Identity, lastUsed time.Time) *refreshError { offlineSessionUpdater := func(old storage.OfflineSessions) (storage.OfflineSessions, error) { if old.Refresh[refresh.ClientID].ID != refresh.ID { - return old, errors.New("refresh token invalid") @@ -277,13 +286,13 @@ index 8ea7ea9e..e8e9b67f 100644 + return old, errors.New("refresh token invalid") + } } + old.Refresh[refresh.ClientID].LastUsed = lastUsed - old.ConnectorData = ident.ConnectorData diff --git a/server/server.go b/server/server.go -index 957b62dc..bc22bf81 100644 +index 1cf71c50..31e6c472 100644 --- a/server/server.go +++ b/server/server.go -@@ -103,6 +103,8 @@ type Config struct { +@@ -120,6 +120,8 @@ type Config struct { PrometheusRegistry *prometheus.Registry HealthChecker gosundheit.Health @@ -292,23 +301,24 @@ index 957b62dc..bc22bf81 100644 } // WebConfig holds the server's frontend templates and asset configuration. -@@ -178,6 +180,8 @@ type Server struct { +@@ -197,6 +199,8 @@ type Server struct { refreshTokenPolicy *RefreshTokenPolicy - logger log.Logger + logger *slog.Logger + + enableMultiRefreshTokens bool } // NewServer constructs a server from the provided config. -@@ -246,20 +250,21 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) +@@ -298,21 +302,22 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) } s := &Server{ - issuerURL: *issuerURL, - connectors: make(map[string]Connector), - storage: newKeyCacher(c.Storage, now), -- supportedResponseTypes: supported, +- supportedResponseTypes: supportedRes, +- supportedGrantTypes: supportedGrants, - idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour), - authRequestsValidFor: value(c.AuthRequestsValidFor, 24*time.Hour), - deviceRequestsValidFor: value(c.DeviceRequestsValidFor, 5*time.Minute), @@ -322,7 +332,8 @@ index 957b62dc..bc22bf81 100644 + issuerURL: *issuerURL, + connectors: make(map[string]Connector), + storage: newKeyCacher(c.Storage, now), -+ supportedResponseTypes: supported, ++ supportedResponseTypes: supportedRes, ++ supportedGrantTypes: supportedGrants, + idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour), + authRequestsValidFor: value(c.AuthRequestsValidFor, 24*time.Hour), + deviceRequestsValidFor: value(c.DeviceRequestsValidFor, 5*time.Minute), @@ -337,7 +348,7 @@ index 957b62dc..bc22bf81 100644 } // Retrieves connector objects in backend storage. This list includes the static connectors -@@ -375,7 +380,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) +@@ -478,7 +483,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy) s.mux = r s.startKeyRotation(ctx, rotationStrategy, now) @@ -346,7 +357,7 @@ index 957b62dc..bc22bf81 100644 return s, nil } -@@ -493,18 +498,18 @@ func (k *keyCacher) GetKeys() (storage.Keys, error) { +@@ -596,19 +601,19 @@ func (k *keyCacher) GetKeys() (storage.Keys, error) { return storageKeys, nil } @@ -360,20 +371,20 @@ index 957b62dc..bc22bf81 100644 case <-time.After(frequency): - if r, err := s.storage.GarbageCollect(now()); err != nil { + if r, err := s.storage.GarbageCollect(now(), unusedRefreshTokensValidFor); err != nil { - s.logger.Errorf("garbage collection failed: %v", err) + s.logger.ErrorContext(ctx, "garbage collection failed", "err", err) } else if !r.IsEmpty() { -- s.logger.Infof("garbage collection run, delete auth requests=%d, auth codes=%d, device requests=%d, device tokens=%d", -- r.AuthRequests, r.AuthCodes, r.DeviceRequests, r.DeviceTokens) -+ s.logger.Infof("garbage collection run, delete auth requests=%d, auth codes=%d, device requests=%d, device tokens=%d, refresh tokens=%d", -+ r.AuthRequests, r.AuthCodes, r.DeviceRequests, r.DeviceTokens, r.RefreshTokens) + s.logger.InfoContext(ctx, "garbage collection run, delete auth", + "requests", r.AuthRequests, "auth_codes", r.AuthCodes, +- "device_requests", r.DeviceRequests, "device_tokens", r.DeviceTokens) ++ "device_requests", r.DeviceRequests, "device_tokens", r.DeviceTokens, "refresh_tokens", r.RefreshTokens) } } } diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go -index dde369c4..a3202c57 100644 +index 84ad1cba..5fb8278d 100644 --- a/storage/conformance/conformance.go +++ b/storage/conformance/conformance.go -@@ -773,8 +773,10 @@ func testGC(t *testing.T, s storage.Storage) { +@@ -785,8 +785,10 @@ func testGC(t *testing.T, s storage.Storage) { t.Fatalf("failed creating auth code: %v", err) } @@ -385,7 +396,7 @@ index dde369c4..a3202c57 100644 if err != nil { t.Errorf("garbage collection failed: %v", err) } else if result.AuthCodes != 0 || result.AuthRequests != 0 { -@@ -785,7 +787,7 @@ func testGC(t *testing.T, s storage.Storage) { +@@ -797,7 +799,7 @@ func testGC(t *testing.T, s storage.Storage) { } } @@ -394,7 +405,7 @@ index dde369c4..a3202c57 100644 t.Errorf("garbage collection failed: %v", err) } else if r.AuthCodes != 1 { t.Errorf("expected to garbage collect 1 objects, got %d", r.AuthCodes) -@@ -824,7 +826,7 @@ func testGC(t *testing.T, s storage.Storage) { +@@ -837,7 +839,7 @@ func testGC(t *testing.T, s storage.Storage) { } for _, tz := range []*time.Location{time.UTC, est, pst} { @@ -403,7 +414,7 @@ index dde369c4..a3202c57 100644 if err != nil { t.Errorf("garbage collection failed: %v", err) } else if result.AuthCodes != 0 || result.AuthRequests != 0 { -@@ -835,7 +837,7 @@ func testGC(t *testing.T, s storage.Storage) { +@@ -848,7 +850,7 @@ func testGC(t *testing.T, s storage.Storage) { } } @@ -412,7 +423,7 @@ index dde369c4..a3202c57 100644 t.Errorf("garbage collection failed: %v", err) } else if r.AuthRequests != 1 { t.Errorf("expected to garbage collect 1 objects, got %d", r.AuthRequests) -@@ -861,7 +863,7 @@ func testGC(t *testing.T, s storage.Storage) { +@@ -874,7 +876,7 @@ func testGC(t *testing.T, s storage.Storage) { } for _, tz := range []*time.Location{time.UTC, est, pst} { @@ -421,7 +432,7 @@ index dde369c4..a3202c57 100644 if err != nil { t.Errorf("garbage collection failed: %v", err) } else if result.DeviceRequests != 0 { -@@ -871,7 +873,7 @@ func testGC(t *testing.T, s storage.Storage) { +@@ -884,7 +886,7 @@ func testGC(t *testing.T, s storage.Storage) { t.Errorf("expected to be able to get auth request after GC: %v", err) } } @@ -430,7 +441,7 @@ index dde369c4..a3202c57 100644 t.Errorf("garbage collection failed: %v", err) } else if r.DeviceRequests != 1 { t.Errorf("expected to garbage collect 1 device request, got %d", r.DeviceRequests) -@@ -897,7 +899,7 @@ func testGC(t *testing.T, s storage.Storage) { +@@ -914,7 +916,7 @@ func testGC(t *testing.T, s storage.Storage) { } for _, tz := range []*time.Location{time.UTC, est, pst} { @@ -439,7 +450,7 @@ index dde369c4..a3202c57 100644 if err != nil { t.Errorf("garbage collection failed: %v", err) } else if result.DeviceTokens != 0 { -@@ -907,7 +909,7 @@ func testGC(t *testing.T, s storage.Storage) { +@@ -924,7 +926,7 @@ func testGC(t *testing.T, s storage.Storage) { t.Errorf("expected to be able to get device token after GC: %v", err) } } @@ -462,10 +473,10 @@ index bc4c1600..6830123e 100644 utcNow := now.UTC() diff --git a/storage/etcd/etcd.go b/storage/etcd/etcd.go -index 63fa7bc2..56e40225 100644 +index f65701ff..f3363ce2 100644 --- a/storage/etcd/etcd.go +++ b/storage/etcd/etcd.go -@@ -38,7 +38,7 @@ func (c *conn) Close() error { +@@ -40,7 +40,7 @@ func (c *conn) Close() error { return c.db.Close() } @@ -475,23 +486,31 @@ index 63fa7bc2..56e40225 100644 defer cancel() authRequests, err := c.listAuthRequests(ctx) diff --git a/storage/kubernetes/storage.go b/storage/kubernetes/storage.go -index 13549ef5..2fdc2bb0 100644 +index 8b6d5c9c..49e6503c 100644 --- a/storage/kubernetes/storage.go +++ b/storage/kubernetes/storage.go -@@ -597,7 +597,7 @@ func (cli *client) UpdateConnector(id string, updater func(a storage.Connector) +@@ -612,7 +612,7 @@ func (cli *client) UpdateConnector(id string, updater func(a storage.Connector) }) } -func (cli *client) GarbageCollect(now time.Time) (result storage.GCResult, err error) { +func (cli *client) GarbageCollect(now time.Time, unusedRefreshTokensValidFor time.Duration) (result storage.GCResult, err error) { var authRequests AuthRequestList - if err := cli.list(resourceAuthRequest, &authRequests); err != nil { + if err := cli.listN(resourceAuthRequest, &authRequests, gcResultLimit); err != nil { return result, fmt.Errorf("failed to list auth requests: %v", err) diff --git a/storage/memory/memory.go b/storage/memory/memory.go -index a9406657..865ef444 100644 +index 4399c61d..69271952 100644 --- a/storage/memory/memory.go +++ b/storage/memory/memory.go -@@ -68,7 +68,7 @@ func (s *memStorage) tx(f func()) { +@@ -3,6 +3,7 @@ package memory + + import ( + "context" ++ "fmt" + "log/slog" + "strings" + "sync" +@@ -71,7 +72,7 @@ func (s *memStorage) tx(f func()) { func (s *memStorage) Close() error { return nil } @@ -500,45 +519,45 @@ index a9406657..865ef444 100644 s.tx(func() { for id, a := range s.authCodes { if now.After(a.Expiry) { -@@ -94,6 +94,24 @@ func (s *memStorage) GarbageCollect(now time.Time) (result storage.GCResult, err - result.DeviceTokens++ +@@ -98,6 +99,24 @@ func (s *memStorage) GarbageCollect(now time.Time) (result storage.GCResult, err } } -+ staleRefreshTokenCutoff := now.Add(-unusedRefreshTokensValidFor) -+ for id, a := range s.refreshTokens { -+ if staleRefreshTokenCutoff.After(a.LastUsed) { -+ // do not delete if this is the primary refresh token linked to offline session -+ o, err := s.GetOfflineSessions(a.Claims.UserID, a.ConnectorID) + }) ++ staleRefreshTokenCutoff := now.Add(-unusedRefreshTokensValidFor) ++ for id, a := range s.refreshTokens { ++ if staleRefreshTokenCutoff.After(a.LastUsed) { ++ // do not delete if this is the primary refresh token linked to offline session ++ o, err := s.GetOfflineSessions(a.Claims.UserID, a.ConnectorID) + -+ if err != nil { -+ s.logger.Errorf("failed to fetch offline session for user_id %v, connector_id %v: %v", a.Claims.UserID, a.ConnectorID, err) ++ if err != nil { ++ s.logger.Error(fmt.Sprintf("failed to fetch offline session for user_id %v, connector_id %v: %v", a.Claims.UserID, a.ConnectorID, err)) ++ } else { ++ if o.Refresh[a.ClientID].ID == id { ++ s.logger.Debug(fmt.Sprintf("not deleting expired primary refresh token")) + } else { -+ if o.Refresh[a.ClientID].ID == id { -+ s.logger.Debugf("not deleting expired primary refresh token") -+ } else { -+ delete(s.refreshTokens, id) -+ result.RefreshTokens++ -+ } ++ delete(s.refreshTokens, id) ++ result.RefreshTokens++ + } + } + } - }) ++ } return result, nil } + diff --git a/storage/sql/crud.go b/storage/sql/crud.go -index 5a234f9d..f685c8fd 100644 +index 1249243c..ca49388d 100644 --- a/storage/sql/crud.go +++ b/storage/sql/crud.go -@@ -84,7 +84,7 @@ type scanner interface { - Scan(dest ...interface{}) error - } +@@ -86,7 +86,7 @@ type scanner interface { + + var _ storage.Storage = (*conn)(nil) -func (c *conn) GarbageCollect(now time.Time) (storage.GCResult, error) { +func (c *conn) GarbageCollect(now time.Time, unusedRefreshTokensValidFor time.Duration) (storage.GCResult, error) { result := storage.GCResult{} r, err := c.Exec(`delete from auth_request where expiry < $1`, now) -@@ -119,6 +119,26 @@ func (c *conn) GarbageCollect(now time.Time) (storage.GCResult, error) { +@@ -121,6 +121,25 @@ func (c *conn) GarbageCollect(now time.Time) (storage.GCResult, error) { result.DeviceTokens = n } @@ -551,21 +570,20 @@ index 5a234f9d..f685c8fd 100644 + // do not delete if this is the primary refresh token linked to offline session + o, err := c.GetOfflineSessions(t.Claims.UserID, t.ConnectorID) + if err != nil { -+ c.logger.Errorf("failed to fetch offline session for user_id %v, connector_id %v: %v", t.Claims.UserID, t.ConnectorID, err) ++ c.logger.Error(fmt.Sprintf("failed to fetch offline session for user_id %v, connector_id %v: %v", t.Claims.UserID, t.ConnectorID, err)) + } else { + if o.Refresh[t.ClientID].ID == t.ID { -+ c.logger.Debugf("not deleting expired primary refresh token") ++ c.logger.Debug(fmt.Sprintf("not deleting expired primary refresh token")) + } else { + c.DeleteRefresh(t.ID) + result.RefreshTokens++ + } + } + } -+ return result, err } -@@ -366,6 +386,35 @@ func getRefresh(q querier, id string) (storage.RefreshToken, error) { +@@ -371,6 +390,35 @@ func getRefresh(q querier, id string) (storage.RefreshToken, error) { `, id)) } @@ -602,10 +620,10 @@ index 5a234f9d..f685c8fd 100644 rows, err := c.Query(` select diff --git a/storage/storage.go b/storage/storage.go -index cdd83ca6..2ea79b8c 100644 +index 03883ef5..b179309c 100644 --- a/storage/storage.go +++ b/storage/storage.go -@@ -53,6 +53,7 @@ type GCResult struct { +@@ -60,6 +60,7 @@ type GCResult struct { AuthCodes int64 DeviceRequests int64 DeviceTokens int64 @@ -613,7 +631,7 @@ index cdd83ca6..2ea79b8c 100644 } // IsEmpty returns whether the garbage collection result is empty or not. -@@ -60,7 +61,8 @@ func (g *GCResult) IsEmpty() bool { +@@ -67,7 +68,8 @@ func (g *GCResult) IsEmpty() bool { return g.AuthRequests == 0 && g.AuthCodes == 0 && g.DeviceRequests == 0 && @@ -623,13 +641,13 @@ index cdd83ca6..2ea79b8c 100644 } // Storage is the storage interface used by the server. Implementations are -@@ -131,8 +133,8 @@ type Storage interface { +@@ -138,8 +140,8 @@ type Storage interface { UpdateDeviceToken(deviceCode string, updater func(t DeviceToken) (DeviceToken, error)) error // GarbageCollect deletes all expired AuthCodes, - // AuthRequests, DeviceRequests, and DeviceTokens. - GarbageCollect(now time.Time) (GCResult, error) -+ // AuthRequests, DeviceRequests, DeviceTokens and RefreshTokens ++ // AuthRequests, DeviceRequests, DeviceTokens, and RefreshTokens + GarbageCollect(now time.Time, unusedRefreshTokensValidFor time.Duration) (GCResult, error) } diff --git a/D/Dex/bundled/patches/03-group-support-oidc.patch b/D/Dex/bundled/patches/03-group-support-oidc.patch index 4e55a14adb1..9549667bb76 100644 --- a/D/Dex/bundled/patches/03-group-support-oidc.patch +++ b/D/Dex/bundled/patches/03-group-support-oidc.patch @@ -1,143 +1,13 @@ -diff --git a/connector/oidc/oidc.go b/connector/oidc/oidc.go -index b752f9da..6d913321 100644 ---- a/connector/oidc/oidc.go -+++ b/connector/oidc/oidc.go -@@ -44,6 +44,9 @@ type Config struct { - // InsecureEnableGroups enables groups claims. This is disabled by default until https://github.com/dexidp/dex/issues/1065 is resolved - InsecureEnableGroups bool `json:"insecureEnableGroups"` - -+ // Allowed groups. -+ AllowedGroups []string `json:"allowedGroups"` // allowedGroups -+ - // GetUserInfo uses the userinfo endpoint to get additional claims for - // the token. This is especially useful where upstreams return "thin" - // id tokens -@@ -149,6 +152,7 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e - hostedDomains: c.HostedDomains, - insecureSkipEmailVerified: c.InsecureSkipEmailVerified, - insecureEnableGroups: c.InsecureEnableGroups, -+ allowedGroups: c.AllowedGroups, - getUserInfo: c.GetUserInfo, - promptType: c.PromptType, - userIDKey: c.UserIDKey, -@@ -174,6 +178,7 @@ type oidcConnector struct { - hostedDomains []string - insecureSkipEmailVerified bool - insecureEnableGroups bool -+ allowedGroups []string - getUserInfo bool - promptType string - userIDKey string -@@ -253,6 +258,15 @@ func (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identit - return c.createIdentity(ctx, identity, token) - } - -+func contains(target string, arr []string) bool { -+ for _, s := range arr { -+ if s == target { -+ return true -+ } -+ } -+ return false -+} -+ - func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.Identity, token *oauth2.Token) (connector.Identity, error) { - rawIDToken, ok := token.Extra("id_token").(string) - if !ok { -@@ -267,6 +281,7 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I - if err := idToken.Claims(&claims); err != nil { - return identity, fmt.Errorf("oidc: failed to decode claims: %v", err) - } -+ c.logger.Debugf("Claims::: %q \n", claims) - - // We immediately want to run getUserInfo if configured before we validate the claims - if c.getUserInfo { -@@ -277,6 +292,7 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I - if err := userInfo.Claims(&claims); err != nil { - return identity, fmt.Errorf("oidc: failed to decode userinfo claims: %v", err) - } -+ c.logger.Debugf("UserInfo::: %q \n", userInfo) - } - - userNameKey := "name" -@@ -341,6 +357,28 @@ func (c *oidcConnector) createIdentity(ctx context.Context, identity connector.I - } - } - } -+ c.logger.Debugf("Groups::: %q \n", groups) -+ -+ // Validate that the user is part of allowedGroups -+ var accessAllowed = false -+ if len(c.allowedGroups) > 0 { -+ for _,group := range c.allowedGroups { -+ groupPresent := contains(group, groups) -+ if groupPresent { -+ accessAllowed = true -+ c.logger.Debugf("Found a match - Group::: %q \n", group) -+ break -+ } -+ } -+ } else { -+ // don't check for groups if allowedGroups is not configured -+ c.logger.Debugf("Group check not performed \n") -+ accessAllowed = true -+ } -+ -+ if !accessAllowed { -+ return identity, errors.New("User is not in the allowed group(s)") -+ } - - hostedDomain, _ := claims["hd"].(string) - if len(c.hostedDomains) > 0 { -diff --git a/go.mod b/go.mod -index f360aa5f..5691198a 100644 ---- a/go.mod -+++ b/go.mod -@@ -3,7 +3,7 @@ module github.com/dexidp/dex - go 1.16 - - require ( -- entgo.io/ent v0.8.0 -+ entgo.io/ent v0.11.0 - github.com/AppsFlyer/go-sundheit v0.4.0 - github.com/Masterminds/semver v1.5.0 - github.com/Masterminds/sprig/v3 v3.2.2 -@@ -18,20 +18,20 @@ require ( - github.com/gorilla/mux v1.8.0 - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 - github.com/kylelemons/godebug v1.1.0 -- github.com/lib/pq v1.10.2 -+ github.com/lib/pq v1.10.5 - github.com/mattermost/xml-roundtrip-validator v0.1.0 -- github.com/mattn/go-sqlite3 v1.14.8 -+ github.com/mattn/go-sqlite3 v1.14.13 - github.com/oklog/run v1.1.0 - github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.11.0 - github.com/russellhaering/goxmldsig v1.1.0 - github.com/sirupsen/logrus v1.8.1 -- github.com/spf13/cobra v1.2.1 -- github.com/stretchr/testify v1.7.0 -+ github.com/spf13/cobra v1.5.0 -+ github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 - go.etcd.io/etcd/client/pkg/v3 v3.5.0 - go.etcd.io/etcd/client/v3 v3.5.0 - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 -- golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 -+ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f - golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 - google.golang.org/api v0.52.0 - google.golang.org/grpc v1.39.0 diff --git a/server/handlers.go b/server/handlers.go -index 2a4f8c71..d35cd83d 100644 +index 0d14eb2a..db1edbbf 100644 --- a/server/handlers.go +++ b/server/handlers.go -@@ -437,7 +437,7 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) - +@@ -473,7 +473,7 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) + if err != nil { - s.logger.Errorf("Failed to authenticate: %v", err) + s.logger.ErrorContext(r.Context(), "failed to authenticate", "err", err) - s.renderError(r, w, http.StatusInternalServerError, fmt.Sprintf("Failed to authenticate: %v", err)) + s.renderError(r, w, http.StatusUnauthorized, fmt.Sprintf("Failed to authenticate: %v", err)) return } - +