Skip to content

Commit 0c3daa5

Browse files
authored
refactor(server): extract userinfo and introspection handlers (#4905)
Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
1 parent 85425a5 commit 0c3daa5

38 files changed

Lines changed: 941 additions & 803 deletions

server/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ func (d dexAPI) GetVersion(ctx context.Context, req *api.VersionReq) (*api.Versi
298298
}
299299

300300
func (d dexAPI) GetDiscovery(ctx context.Context, req *api.DiscoveryReq) (*api.DiscoveryResp, error) {
301-
discoveryDoc := d.server.discovery.Construct(ctx)
301+
discoveryDoc := d.server.constructDiscovery(ctx)
302302
data, err := json.Marshal(discoveryDoc)
303303
if err != nil {
304304
return nil, fmt.Errorf("failed to marshal discovery data: %v", err)

server/approval.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"github.com/dexidp/dex/pkg/featureflags"
13+
"github.com/dexidp/dex/server/oauth2"
1314
"github.com/dexidp/dex/server/tokens"
1415
"github.com/dexidp/dex/storage"
1516
)
@@ -151,7 +152,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
151152

152153
for _, responseType := range authReq.ResponseTypes {
153154
switch responseType {
154-
case responseTypeCode:
155+
case oauth2.ResponseTypeCode:
155156
code = storage.AuthCode{
156157
ID: storage.NewID(),
157158
ClientID: authReq.ClientID,
@@ -173,13 +174,13 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
173174

174175
// Implicit and hybrid flows that try to use the OOB redirect URI are
175176
// rejected earlier. If we got here we're using the code flow.
176-
if authReq.RedirectURI == redirectURIOOB {
177+
if authReq.RedirectURI == oauth2.RedirectURIOOB {
177178
if err := s.templates.OOB(r, w, code.ID); err != nil {
178179
s.logger.ErrorContext(r.Context(), "server template error", "err", err)
179180
}
180181
return
181182
}
182-
case responseTypeToken:
183+
case oauth2.ResponseTypeToken:
183184
implicitOrHybrid = true
184185
var err error
185186

@@ -193,10 +194,10 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
193194
})
194195
if err != nil {
195196
s.logger.ErrorContext(r.Context(), "failed to create new access token", "err", err)
196-
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
197+
s.tokenErrHelper(w, oauth2.ServerError, "", http.StatusInternalServerError)
197198
return
198199
}
199-
case responseTypeIDToken:
200+
case oauth2.ResponseTypeIDToken:
200201
implicitOrHybrid = true
201202
var err error
202203

@@ -210,7 +211,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
210211
}, accessToken, code.ID)
211212
if err != nil {
212213
s.logger.ErrorContext(r.Context(), "failed to create ID token", "err", err)
213-
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
214+
s.tokenErrHelper(w, oauth2.ServerError, "", http.StatusInternalServerError)
214215
return
215216
}
216217
}

server/approval_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/stretchr/testify/require"
1515

16+
"github.com/dexidp/dex/server/oauth2"
1617
"github.com/dexidp/dex/storage"
1718
)
1819

@@ -106,7 +107,7 @@ func TestSkipApprovalWithExistingConsent(t *testing.T) {
106107
ClientID: tc.clientID,
107108
RedirectURI: "cb",
108109
Expiry: expiry,
109-
ResponseTypes: []string{responseTypeCode},
110+
ResponseTypes: []string{oauth2.ResponseTypeCode},
110111
Scopes: tc.scopes,
111112
ForceApprovalPrompt: tc.forcePrompt,
112113
}
@@ -149,7 +150,7 @@ func TestConsentPersistedOnApproval(t *testing.T) {
149150
ID: "approval-consent-test",
150151
ClientID: clientID,
151152
ConnectorID: connectorID,
152-
ResponseTypes: []string{responseTypeCode},
153+
ResponseTypes: []string{oauth2.ResponseTypeCode},
153154
RedirectURI: "https://client.example/callback",
154155
Expiry: time.Now().Add(time.Minute),
155156
LoggedIn: true,
@@ -355,7 +356,7 @@ func TestHandleApprovalDoubleSubmitPOST(t *testing.T) {
355356
authReq := storage.AuthRequest{
356357
ID: "approval-double-submit",
357358
ClientID: "test",
358-
ResponseTypes: []string{responseTypeCode},
359+
ResponseTypes: []string{oauth2.ResponseTypeCode},
359360
RedirectURI: "https://client.example/callback",
360361
Expiry: time.Now().Add(time.Minute),
361362
LoggedIn: true,

server/authorize.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,24 @@ import (
1010
"net/url"
1111
"strings"
1212

13+
"github.com/dexidp/dex/server/oauth2"
1314
"github.com/dexidp/dex/server/templates"
1415
"github.com/dexidp/dex/storage"
1516
)
1617

1718
// grantTypeFromAuthRequest determines the grant type from the authorization request parameters.
1819
func (s *Server) grantTypeFromAuthRequest(r *http.Request) string {
1920
redirectURI := r.Form.Get("redirect_uri")
20-
if redirectURI == deviceCallbackURI || strings.HasSuffix(redirectURI, deviceCallbackURI) {
21-
return grantTypeDeviceCode
21+
if redirectURI == oauth2.DeviceCallbackURI || strings.HasSuffix(redirectURI, oauth2.DeviceCallbackURI) {
22+
return oauth2.GrantTypeDeviceCode
2223
}
2324
responseType := r.Form.Get("response_type")
2425
for _, rt := range strings.Fields(responseType) {
2526
if rt == "token" || rt == "id_token" {
26-
return grantTypeImplicit
27+
return oauth2.GrantTypeImplicit
2728
}
2829
}
29-
return grantTypeAuthorizationCode
30+
return oauth2.GrantTypeAuthorizationCode
3031
}
3132

3233
// handleAuthorization handles the OAuth2 auth endpoint.
@@ -117,7 +118,7 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
117118
prompt, err := ParsePrompt(authReq.Prompt)
118119
if err != nil {
119120
// Server error because authReq was validated before saving it to database.
120-
s.redirectWithError(w, r, authReq, errServerError, "Invalid authentication request")
121+
s.redirectWithError(w, r, authReq, oauth2.ServerError, "Invalid authentication request")
121122
return
122123
}
123124

@@ -137,7 +138,7 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
137138
}
138139
if prompt.None() {
139140
// Cannot authenticate silently with prompt=none.
140-
s.redirectWithError(w, r, authReq, errLoginRequired, "id_token_hint does not match authenticated user")
141+
s.redirectWithError(w, r, authReq, oauth2.LoginRequired, "id_token_hint does not match authenticated user")
141142
return
142143
}
143144
}

server/authorize_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/stretchr/testify/require"
1212

13+
"github.com/dexidp/dex/server/oauth2"
1314
"github.com/dexidp/dex/storage"
1415
)
1516

@@ -28,7 +29,7 @@ func TestHandleAuthorizationConnectorGrantTypeFiltering(t *testing.T) {
2829
{
2930
name: "one connector filtered, redirect to remaining",
3031
connectorGrantTypes: map[string][]string{
31-
"mock": {grantTypeDeviceCode},
32+
"mock": {oauth2.GrantTypeDeviceCode},
3233
"mock2": nil,
3334
},
3435
responseType: "code",
@@ -38,8 +39,8 @@ func TestHandleAuthorizationConnectorGrantTypeFiltering(t *testing.T) {
3839
{
3940
name: "all connectors filtered",
4041
connectorGrantTypes: map[string][]string{
41-
"mock": {grantTypeDeviceCode},
42-
"mock2": {grantTypeDeviceCode},
42+
"mock": {oauth2.GrantTypeDeviceCode},
43+
"mock2": {oauth2.GrantTypeDeviceCode},
4344
},
4445
responseType: "code",
4546
wantCode: http.StatusBadRequest,
@@ -57,7 +58,7 @@ func TestHandleAuthorizationConnectorGrantTypeFiltering(t *testing.T) {
5758
{
5859
name: "implicit flow filters auth_code-only connector",
5960
connectorGrantTypes: map[string][]string{
60-
"mock": {grantTypeAuthorizationCode},
61+
"mock": {oauth2.GrantTypeAuthorizationCode},
6162
"mock2": nil,
6263
},
6364
responseType: "token",

server/connectorauth.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,28 @@ package server
2424

2525
import (
2626
"net/http"
27+
"slices"
2728

29+
"github.com/dexidp/dex/server/oauth2"
2830
"github.com/dexidp/dex/storage"
2931
)
3032

33+
// ConnectorGrantTypes is the set of grant types that can be restricted per connector.
34+
var ConnectorGrantTypes = map[string]bool{
35+
oauth2.GrantTypeAuthorizationCode: true,
36+
oauth2.GrantTypeRefreshToken: true,
37+
oauth2.GrantTypeImplicit: true,
38+
oauth2.GrantTypePassword: true,
39+
oauth2.GrantTypeDeviceCode: true,
40+
oauth2.GrantTypeTokenExchange: true,
41+
}
42+
43+
// GrantTypeAllowed checks if the given grant type is allowed for this connector.
44+
// If no grant types are configured, all are allowed.
45+
func GrantTypeAllowed(configuredTypes []string, grantType string) bool {
46+
return len(configuredTypes) == 0 || slices.Contains(configuredTypes, grantType)
47+
}
48+
3149
// filterConnectors filters the list of connectors by the allowed connector IDs.
3250
// If allowedConnectors is empty, all connectors are returned (no filtering).
3351
func filterConnectors(connectors []storage.Connector, allowedConnectors []string) []storage.Connector {
@@ -77,6 +95,6 @@ func (s *Server) checkConnectorAllowed(w http.ResponseWriter, r *http.Request, c
7795
}
7896
s.logger.WarnContext(r.Context(), "connector not allowed for client",
7997
"client_id", client.ID, "connector_id", connID)
80-
s.tokenErrHelper(w, errInvalidGrant, "Connector not allowed for this client.", http.StatusBadRequest)
98+
s.tokenErrHelper(w, oauth2.InvalidGrant, "Connector not allowed for this client.", http.StatusBadRequest)
8199
return false
82100
}

server/device_authorize.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"strings"
1616
"time"
1717

18+
"github.com/dexidp/dex/server/oauth2"
1819
"github.com/dexidp/dex/storage"
1920
)
2021

@@ -67,7 +68,7 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
6768
err := r.ParseForm()
6869
if err != nil {
6970
s.logger.ErrorContext(r.Context(), "could not parse Device Request body", "err", err)
70-
s.tokenErrHelper(w, errInvalidRequest, "", http.StatusNotFound)
71+
s.tokenErrHelper(w, oauth2.InvalidRequest, "", http.StatusNotFound)
7172
return
7273
}
7374

@@ -83,7 +84,7 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
8384
}
8485
if codeChallengeMethod != codeChallengeMethodS256 && codeChallengeMethod != codeChallengeMethodPlain {
8586
description := fmt.Sprintf("Unsupported PKCE challenge method (%q).", codeChallengeMethod)
86-
s.tokenErrHelper(w, errInvalidRequest, description, http.StatusBadRequest)
87+
s.tokenErrHelper(w, oauth2.InvalidRequest, description, http.StatusBadRequest)
8788
return
8889
}
8990

@@ -116,14 +117,14 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
116117

117118
if err := s.storage.CreateDeviceRequest(ctx, deviceReq); err != nil {
118119
s.logger.ErrorContext(r.Context(), "failed to store device request", "err", err)
119-
s.tokenErrHelper(w, errInvalidRequest, "", http.StatusInternalServerError)
120+
s.tokenErrHelper(w, oauth2.InvalidRequest, "", http.StatusInternalServerError)
120121
return
121122
}
122123

123124
// Store the device token
124125
deviceToken := storage.DeviceToken{
125126
DeviceCode: deviceCode,
126-
Status: deviceTokenPending,
127+
Status: oauth2.DeviceTokenPending,
127128
Expiry: expireTime,
128129
LastRequestTime: s.now(),
129130
PollIntervalSeconds: 0,
@@ -135,14 +136,14 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
135136

136137
if err := s.storage.CreateDeviceToken(ctx, deviceToken); err != nil {
137138
s.logger.ErrorContext(r.Context(), "failed to store device token", "err", err)
138-
s.tokenErrHelper(w, errInvalidRequest, "", http.StatusInternalServerError)
139+
s.tokenErrHelper(w, oauth2.InvalidRequest, "", http.StatusInternalServerError)
139140
return
140141
}
141142

142143
u, err := url.Parse(s.issuerURL.String())
143144
if err != nil {
144145
s.logger.ErrorContext(r.Context(), "could not parse issuer URL", "err", err)
145-
s.tokenErrHelper(w, errInvalidRequest, "", http.StatusInternalServerError)
146+
s.tokenErrHelper(w, oauth2.InvalidRequest, "", http.StatusInternalServerError)
146147
return
147148
}
148149
u.Path = path.Join(u.Path, "device")
@@ -177,7 +178,7 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
177178

178179
default:
179180
s.renderError(r, w, http.StatusBadRequest, "Invalid device code request type")
180-
s.tokenErrHelper(w, errInvalidRequest, "", http.StatusBadRequest)
181+
s.tokenErrHelper(w, oauth2.InvalidRequest, "", http.StatusBadRequest)
181182
}
182183
}
183184

@@ -225,7 +226,7 @@ func (s *Server) verifyUserCode(w http.ResponseWriter, r *http.Request) {
225226
q.Set("client_secret", deviceRequest.ClientSecret)
226227
q.Set("state", deviceRequest.UserCode)
227228
q.Set("response_type", "code")
228-
q.Set("redirect_uri", s.absPath(deviceCallbackURI))
229+
q.Set("redirect_uri", s.absPath(oauth2.DeviceCallbackURI))
229230
q.Set("scope", strings.Join(deviceRequest.Scopes, " "))
230231
u.RawQuery = q.Encode()
231232

server/device_callback.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"net/http"
1111

12+
"github.com/dexidp/dex/server/oauth2"
1213
"github.com/dexidp/dex/storage"
1314
)
1415

@@ -61,14 +62,14 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) {
6162
if err != nil {
6263
if err != storage.ErrNotFound {
6364
s.logger.ErrorContext(r.Context(), "failed to get client", "err", err)
64-
s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
65+
s.tokenErrHelper(w, oauth2.ServerError, "", http.StatusInternalServerError)
6566
} else {
66-
s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized)
67+
s.tokenErrHelper(w, oauth2.InvalidClient, "Invalid client credentials.", http.StatusUnauthorized)
6768
}
6869
return
6970
}
7071
if client.Secret != deviceReq.ClientSecret {
71-
s.tokenErrHelper(w, errInvalidClient, "Invalid client credentials.", http.StatusUnauthorized)
72+
s.tokenErrHelper(w, oauth2.InvalidClient, "Invalid client credentials.", http.StatusUnauthorized)
7273
return
7374
}
7475

@@ -92,7 +93,7 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) {
9293
}
9394

9495
updater := func(old storage.DeviceToken) (storage.DeviceToken, error) {
95-
if old.Status == deviceTokenComplete {
96+
if old.Status == oauth2.DeviceTokenComplete {
9697
return old, errors.New("device token already complete")
9798
}
9899
respStr, err := json.MarshalIndent(resp, "", " ")
@@ -103,7 +104,7 @@ func (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) {
103104
}
104105

105106
old.Token = string(respStr)
106-
old.Status = deviceTokenComplete
107+
old.Status = oauth2.DeviceTokenComplete
107108
return old, nil
108109
}
109110

0 commit comments

Comments
 (0)