Skip to content

Commit 63cfbea

Browse files
authored
feat: Allow io.cozy.contacts and io.cozy.contacts.groups in token exc… (#4745)
…hange Replace the single allowed scope constant with an allowlist and a validateTokenExchangeScope function that accepts any space-separated combination of io.cozy.files, io.cozy.contacts, and io.cozy.contacts.groups.
2 parents 80aaa64 + 05f3b8a commit 63cfbea

4 files changed

Lines changed: 38 additions & 6 deletions

File tree

docs/auth.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ The target Cozy instance is the request host. The exchanged `id_token` must:
10681068
The request body is JSON:
10691069

10701070
- `id_token`, the external OIDC token
1071-
- `scope`, currently limited to `io.cozy.files`
1071+
- `scope`, a space-separated list of allowed doctypes: `io.cozy.files`, `io.cozy.contacts`, `io.cozy.contacts.groups`
10721072

10731073
Example:
10741074

web/auth/auth_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2503,12 +2503,12 @@ func TestTokenExchange(t *testing.T) {
25032503
WithHeader("Origin", "https://admin.example.com").
25042504
WithJSON(map[string]string{
25052505
"id_token": idToken,
2506-
"scope": "io.cozy.contacts",
2506+
"scope": "io.cozy.unknown",
25072507
}).
25082508
Expect().
25092509
Status(http.StatusBadRequest).
25102510
JSON().Object().
2511-
ValueEqual("error", "invalid scope")
2511+
ValueEqual("error", `scope "io.cozy.unknown" is not allowed`)
25122512
})
25132513
}
25142514

web/auth/token_exchange.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,16 @@ import (
2525
const (
2626
tokenExchangeAdminRole = "admin"
2727
tokenExchangeOwnerRole = "owner"
28-
tokenExchangeAllowedScope = "io.cozy.files"
2928
tokenExchangeOAuthClientName = "Twake Admin Panel"
3029
tokenExchangeOAuthClientSoftwareID = "twake-admin-panel"
3130
)
3231

32+
var tokenExchangeAllowedScopes = map[string]struct{}{
33+
"io.cozy.files": {},
34+
"io.cozy.contacts": {},
35+
"io.cozy.contacts.groups": {},
36+
}
37+
3338
type tokenExchangeRequest struct {
3439
IDToken string `json:"id_token"`
3540
Scope string `json:"scope"`
@@ -47,8 +52,8 @@ func executeTokenExchange(c echo.Context, inst *instance.Instance, req tokenExch
4752
if err != nil {
4853
return nil, echo.NewHTTPError(http.StatusBadRequest, err.Error())
4954
}
50-
if req.Scope != tokenExchangeAllowedScope {
51-
return nil, echo.NewHTTPError(http.StatusBadRequest, "invalid scope")
55+
if err := validateTokenExchangeScope(req.Scope); err != nil {
56+
return nil, echo.NewHTTPError(http.StatusBadRequest, err.Error())
5257
}
5358

5459
client, err := createTokenExchangeOAuthClient(c, inst)
@@ -67,6 +72,18 @@ func executeTokenExchange(c echo.Context, inst *instance.Instance, req tokenExch
6772
return buildTokenExchangeResponse(inst, client, req.Scope)
6873
}
6974

75+
func validateTokenExchangeScope(scope string) error {
76+
if scope == "" {
77+
return errors.New("scope is required")
78+
}
79+
for _, s := range strings.Split(scope, " ") {
80+
if _, ok := tokenExchangeAllowedScopes[s]; !ok {
81+
return fmt.Errorf("scope %q is not allowed", s)
82+
}
83+
}
84+
return nil
85+
}
86+
7087
func validateTokenExchangeIDToken(inst *instance.Instance, raw string) (jwt.MapClaims, error) {
7188
if inst == nil {
7289
return nil, errors.New("instance is missing")

web/auth/token_exchange_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package auth
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestValidateTokenExchangeScope(t *testing.T) {
10+
assert.Error(t, validateTokenExchangeScope(""))
11+
assert.Error(t, validateTokenExchangeScope("io.cozy.unknown"))
12+
assert.Error(t, validateTokenExchangeScope("io.cozy.files\tio.cozy.contacts\tio.cozy.contacts.groups"))
13+
assert.NoError(t, validateTokenExchangeScope("io.cozy.files"))
14+
assert.NoError(t, validateTokenExchangeScope("io.cozy.files io.cozy.contacts io.cozy.contacts.groups"))
15+
}

0 commit comments

Comments
 (0)