Skip to content

Commit c673572

Browse files
committed
refactor(server): apply code-review fixes
- Replace hand-rolled membership loops with slices.Contains in shouldIssueRefreshToken, tokens.HasOpenID and tokens.CrossClientTrusted. - Hoist the constant password/client_credentials scope policies to package vars so ScopePolicy() no longer allocates the maps on every request. - Harden Endpoint.writeError against a typed-nil *oauth2.Error so a future grant returning one cannot panic the token endpoint. - Correct the ConnectorID doc: returning "" skips the connector step, which authorization_code does deliberately (gated at /auth). Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
1 parent 797cdce commit c673572

5 files changed

Lines changed: 46 additions & 50 deletions

File tree

server/grants/clientcredentials.go

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,22 @@ func (g *clientCredentials) RequiresClientAuth() bool {
2222
return true
2323
}
2424

25+
var clientCredentialsScopePolicy = ScopePolicy{
26+
Standard: map[string]bool{
27+
tokens.ScopeOpenID: true,
28+
tokens.ScopeEmail: true,
29+
tokens.ScopeProfile: true,
30+
tokens.ScopeGroups: true,
31+
},
32+
Rejected: map[string]string{
33+
tokens.ScopeOfflineAccess: "client_credentials grant does not support offline_access scope.",
34+
tokens.ScopeFederatedID: "client_credentials grant does not support federated:id scope.",
35+
},
36+
ErrorType: oauth2.InvalidScope,
37+
}
38+
2539
func (g *clientCredentials) ScopePolicy() ScopePolicy {
26-
return ScopePolicy{
27-
Standard: map[string]bool{
28-
tokens.ScopeOpenID: true,
29-
tokens.ScopeEmail: true,
30-
tokens.ScopeProfile: true,
31-
tokens.ScopeGroups: true,
32-
},
33-
Rejected: map[string]string{
34-
tokens.ScopeOfflineAccess: "client_credentials grant does not support offline_access scope.",
35-
tokens.ScopeFederatedID: "client_credentials grant does not support federated:id scope.",
36-
},
37-
ErrorType: oauth2.InvalidScope,
38-
}
40+
return clientCredentialsScopePolicy
3941
}
4042

4143
// ConnectorID is empty: client_credentials involves no connector.

server/grants/grants.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"log/slog"
88
"net/http"
99
"net/url"
10+
"slices"
1011
"strings"
1112
"time"
1213

@@ -103,11 +104,14 @@ type Grant interface {
103104
// ScopePolicy reports how the endpoint validates the requested scopes for
104105
// this grant.
105106
ScopePolicy() ScopePolicy
106-
// ConnectorID is the connector this grant authenticates against, or "" when
107-
// it uses none (client_credentials). The grant may read it from the request
108-
// or look it up in storage. The endpoint then resolves it and enforces the
109-
// connector-authorization invariant before Authorize, so a grant cannot
110-
// forget the check. Returning an error rejects the request.
107+
// ConnectorID is the connector this grant authenticates against; the endpoint
108+
// resolves it and enforces the connector-authorization invariant (client
109+
// allows it, connector allows the grant type) before Authorize. The grant may
110+
// read it from the request or look it up in storage; returning an error
111+
// rejects the request. Returning "" skips the step — for a grant that uses no
112+
// connector (client_credentials, device_code), or one already authorized
113+
// elsewhere (authorization_code was gated at /auth and resolves its connector
114+
// inside Authorize only to decide on a refresh token).
111115
ConnectorID(ctx context.Context, req *Request, client storage.Client) (string, *oauth2.Error)
112116
// Authorize turns the validated request into the authorization to issue
113117
// tokens for, proving the resource owner's identity against conn (the zero
@@ -373,9 +377,10 @@ func (e *Endpoint) authenticateClient(ctx context.Context, w http.ResponseWriter
373377
// writeError writes err as an OAuth2 error response. An *oauth2.Error carries its
374378
// own type/description/status; anything else is reported as a server error.
375379
func (e *Endpoint) writeError(ctx context.Context, w http.ResponseWriter, err error) {
376-
oerr := &oauth2.Error{Type: oauth2.ServerError, Status: http.StatusInternalServerError}
377-
if !errors.As(err, &oerr) {
380+
var oerr *oauth2.Error
381+
if !errors.As(err, &oerr) || oerr == nil {
378382
e.logger.ErrorContext(ctx, "token request failed", "err", err)
383+
oerr = &oauth2.Error{Type: oauth2.ServerError, Status: http.StatusInternalServerError}
379384
}
380385
if werr := oauth2.WriteError(w, oerr.Type, oerr.Description, oerr.Status); werr != nil {
381386
e.logger.ErrorContext(ctx, "failed to write token error response", "err", werr)
@@ -392,10 +397,5 @@ func shouldIssueRefreshToken(conn connectors.Connector, scopes []string) bool {
392397
if !connectors.GrantTypeAllowed(conn.GrantTypes, oauth2.GrantTypeRefreshToken) {
393398
return false
394399
}
395-
for _, scope := range scopes {
396-
if scope == tokens.ScopeOfflineAccess {
397-
return true
398-
}
399-
}
400-
return false
400+
return slices.Contains(scopes, tokens.ScopeOfflineAccess)
401401
}

server/grants/password.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,21 @@ func (g *password) RequiresClientAuth() bool {
2727
return true
2828
}
2929

30+
var passwordScopePolicy = ScopePolicy{
31+
Standard: map[string]bool{
32+
tokens.ScopeOpenID: true,
33+
tokens.ScopeOfflineAccess: true,
34+
tokens.ScopeEmail: true,
35+
tokens.ScopeProfile: true,
36+
tokens.ScopeGroups: true,
37+
tokens.ScopeFederatedID: true,
38+
},
39+
RequireOpenID: true,
40+
ErrorType: oauth2.InvalidRequest,
41+
}
42+
3043
func (g *password) ScopePolicy() ScopePolicy {
31-
return ScopePolicy{
32-
Standard: map[string]bool{
33-
tokens.ScopeOpenID: true,
34-
tokens.ScopeOfflineAccess: true,
35-
tokens.ScopeEmail: true,
36-
tokens.ScopeProfile: true,
37-
tokens.ScopeGroups: true,
38-
tokens.ScopeFederatedID: true,
39-
},
40-
RequireOpenID: true,
41-
ErrorType: oauth2.InvalidRequest,
42-
}
44+
return passwordScopePolicy
4345
}
4446

4547
// ConnectorID is the connector the password grant is configured to use.

server/tokens/crossclient.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tokens
22

33
import (
44
"context"
5+
"slices"
56

67
"github.com/dexidp/dex/storage"
78
)
@@ -20,10 +21,5 @@ func CrossClientTrusted(ctx context.Context, s storage.Storage, clientID, peerID
2021
}
2122
return false, err
2223
}
23-
for _, id := range peer.TrustedPeers {
24-
if id == clientID {
25-
return true, nil
26-
}
27-
}
28-
return false, nil
24+
return slices.Contains(peer.TrustedPeers, clientID), nil
2925
}

server/tokens/scopes.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tokens
22

33
import (
4+
"slices"
45
"strings"
56

67
"github.com/dexidp/dex/connector"
@@ -21,12 +22,7 @@ const (
2122
// HasOpenID reports whether the openid scope was requested, i.e. whether an
2223
// ID token should be issued.
2324
func HasOpenID(scopes []string) bool {
24-
for _, scope := range scopes {
25-
if scope == ScopeOpenID {
26-
return true
27-
}
28-
}
29-
return false
25+
return slices.Contains(scopes, ScopeOpenID)
3026
}
3127

3228
// ParseCrossClientScope extracts the peer client ID from a cross-client audience

0 commit comments

Comments
 (0)