Skip to content

Commit 6e56533

Browse files
committed
fix: check jwt signed token
1 parent 6a1a2f3 commit 6e56533

File tree

6 files changed

+67
-29
lines changed

6 files changed

+67
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Enhancement: More secure Microsoft 365 collaboration
2+
3+
Security update to WOPI protocol token handling
4+
5+
https://github.com/owncloud/ocis/pull/11276

services/collaboration/pkg/connector/fileconnector_test.go

+31-12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"path"
99
"regexp"
1010
"strings"
11+
"time"
1112

1213
appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
1314
auth "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
@@ -16,9 +17,9 @@ import (
1617
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
1718
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
1819
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
20+
"github.com/golang-jwt/jwt/v5"
1921
. "github.com/onsi/ginkgo/v2"
2022
. "github.com/onsi/gomega"
21-
"github.com/owncloud/ocis/v2/ocis-pkg/conversions"
2223
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
2324
collabmocks "github.com/owncloud/ocis/v2/services/collaboration/mocks"
2425
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
@@ -57,7 +58,7 @@ var _ = Describe("FileConnector", func() {
5758
WopiSrc: "https://ocis.server.prv",
5859
Secret: "topsecret",
5960
},
60-
TokenManager: &config.TokenManager{JWTSecret: "secret"},
61+
TokenManager: &config.TokenManager{JWTSecret: "topsecret"},
6162
}
6263
ccs = &collabmocks.ContentConnectorService{}
6364

@@ -67,10 +68,29 @@ var _ = Describe("FileConnector", func() {
6768
gatewaySelector.On("Next").Return(gatewayClient, nil)
6869
fc = connector.NewFileConnector(gatewaySelector, cfg, nil)
6970

71+
// Generate a valid token for testing
72+
now := time.Now()
73+
claims := jwt.MapClaims{
74+
"aud": "web",
75+
"exp": now.Add(24 * time.Hour).Unix(),
76+
"iat": now.Unix(),
77+
"iss": "https://ocis.jp.solidgear.prv",
78+
"jti": "fBWi7AXhQPUhah2CWEPTQKpCfgpFlAiL",
79+
"lg.i": map[string]interface{}{
80+
"dn": "bro",
81+
"id": "ownCloudUUID=faf11647-7451-4b9a-bffe-3b5ddcc5972b",
82+
"un": "brotato",
83+
},
84+
"lg.p": "identifier-ldap",
85+
"lg.t": "1",
86+
"scp": "openid profile email",
87+
"sub": "cAvuzX8gXLZdiXx-@1NWTJtCPDqUJ44nt46FtD9p5L7tjePFdZMVJ9E30Nx2-dui7HKCLxAiaCTtbX511JcdHw",
88+
}
89+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
90+
accessToken, _ := token.SignedString([]byte(cfg.Wopi.Secret))
91+
7092
wopiCtx = middleware.WopiContext{
71-
// a real token is needed for the PutRelativeFileSuggested tests
72-
// although we aren't checking anything inside the token
73-
AccessToken: "eyJhbGciOiJQUzI1NiIsImtpZCI6InByaXZhdGUta2V5IiwidHlwIjoiSldUIn0.eyJhdWQiOiJ3ZWIiLCJleHAiOjE3MjAwOTIyODAsImlhdCI6MTcyMDA5MTk4MCwiaXNzIjoiaHR0cHM6Ly9vY2lzLmpwLnNvbGlkZ2Vhci5wcnYiLCJqdGkiOiJmQldpN0FYaFFQdUhhaDJDV0VQVFFLcENmZ3BGbEFpTCIsImxnLmkiOnsiZG4iOiJicm8iLCJpZCI6Im93bkNsb3VkVVVJRD1mYWYxMTY0Ny03NDUxLTRiOWEtYmZmZS0zYjVkZGNjNTk3MmIiLCJ1biI6ImJyb3RhdG8ifSwibGcucCI6ImlkZW50aWZpZXItbGRhcCIsImxnLnQiOiIxIiwic2NwIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJzdWIiOiJjQXZ1elg4Z1hMWmRpWHgtQDFOV1RKdENQRHFVSjQ0bnQ0NkZ0RDlwNUw3dGplUEZkWk1WSjlFMzBOeDItZHVpN0hLQ0x4QWlXYUNUdGJYNTExSmNkSHcifQ.StpQpE4ipxk8Nhk6xgob1Tovbk6bcUVs5-fkej2hIoKoJKfR2OY-CiFQ3wwgEcFro8notxeVfOmxs36z_ezFeJBZRbxpSggcr77LFtQwlsWvD5AuAgLZN1otdvULehunXE_DtxRJZ1rqnsOBT03zKOZLx8Q7QTy6DeRuf1KQtCIowa9D4ymPM4TTmtQdiW2XjByO3OCLFEMVBfDFGPibR6gMnftGQ5kfiZGDTUVCauEXwE-msZVZ42QY-wFRppX_RIL1Z0p6T4dr_6_y-VM1lNYJ5-dB5c5rg_c03Xu1y_TIxs31-8--dtUyZmBVOZFk8bB9msNk-iaOEjzKeUZLymo_-2qVYvXxzNrkq1QA8luaLR6jec_CRT2P8wsB2nyebFU6_myKe34m6f8uqGhOzcOwPB4TpoxPx4ucQgo1CQJwQZHZsZ7Q6TVYZUXJdWwzzMuvJXmnn36iybw0Ub6On4sGKj3gHetjoJg8VnL-TQkBvf1iHX2ktRG3Nq2rnPrB2OTpi2rLpleWg_s8Y8FXxIgYqM0JG8kO1n5RPGMeYQG7qd6f9wdcaPIvgxCa_HsZtMr7eGcDzZtxp-NivgJOS6ode0ZAJ3wGU-AVhmyshpds3DFECcvkBcP_4dD52AXiAq9X3UVkVdNsxs_yB9P7zBcdsKsD6QDJv5gf-6DEu34",
93+
AccessToken: accessToken,
7494
FileReference: &providerv1beta1.Reference{
7595
ResourceId: &providerv1beta1.ResourceId{
7696
StorageId: "abc",
@@ -1754,7 +1774,7 @@ var _ = Describe("FileConnector", func() {
17541774
OpaqueId: "aabbcc",
17551775
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
17561776
},
1757-
Size: uint64(998877),
1777+
// Size is intentionally nil for guest users
17581778
Mtime: &typesv1beta1.Timestamp{
17591779
Seconds: uint64(16273849),
17601780
},
@@ -1779,7 +1799,7 @@ var _ = Describe("FileConnector", func() {
17791799

17801800
expectedFileInfo := &fileinfo.Collabora{
17811801
OwnerID: "61616262636340637573746f6d496470", // hex of aabbcc@customIdp
1782-
Size: int64(998877),
1802+
Size: 0,
17831803
BaseFileName: "test.txt",
17841804
UserCanNotWriteRelative: false,
17851805
DisableExport: true,
@@ -1838,7 +1858,7 @@ var _ = Describe("FileConnector", func() {
18381858
Decoder: "json",
18391859
Value: val,
18401860
},
1841-
Role: auth.Role(appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY), //
1861+
Role: auth.Role(appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY),
18421862
},
18431863
}
18441864
// change view mode to view only
@@ -1856,7 +1876,7 @@ var _ = Describe("FileConnector", func() {
18561876
OpaqueId: "aabbcc",
18571877
Type: userv1beta1.UserType_USER_TYPE_PRIMARY,
18581878
},
1859-
Size: uint64(998877),
1879+
// Size is intentionally nil for guest users
18601880
Mtime: &typesv1beta1.Timestamp{
18611881
Seconds: uint64(16273849),
18621882
},
@@ -1877,11 +1897,10 @@ var _ = Describe("FileConnector", func() {
18771897

18781898
expectedFileInfo := &fileinfo.OnlyOffice{
18791899
Version: "v162738490",
1880-
Size: conversions.ToPointer(int64(998877)),
18811900
BaseFileName: "test.txt",
18821901
BreadcrumbDocName: "test.txt",
18831902
BreadcrumbFolderName: "/path/to",
1884-
BreadcrumbFolderURL: "https://ocis.example.prv/s/ABC123",
1903+
BreadcrumbFolderURL: "https://ocis.example.prv/s/ABC123", // Match share token format
18851904
DisablePrint: true,
18861905
UserCanNotWriteRelative: false,
18871906
SupportsLocks: true,
@@ -1891,7 +1910,6 @@ var _ = Describe("FileConnector", func() {
18911910
UserCanRename: false,
18921911
UserCanReview: false,
18931912
UserCanWrite: false,
1894-
EnableInsertRemoteImage: false,
18951913
UserID: "guest-zzz000",
18961914
UserFriendlyName: "guest zzz000",
18971915
FileSharingURL: "https://ocis.example.prv/f/storageid$spaceid%21opaqueid?details=sharing",
@@ -2047,6 +2065,7 @@ var _ = Describe("FileConnector", func() {
20472065
FileVersionURL: "https://ocis.example.prv/f/storageid$spaceid%21opaqueid?details=versions",
20482066
HostEditURL: "https://ocis.example.prv/external-onlyoffice/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write",
20492067
PostMessageOrigin: "https://ocis.example.prv",
2068+
TemplateSource: "", // Remove the hardcoded token since it's dynamically generated
20502069
}
20512070

20522071
// change wopi app provider

services/collaboration/pkg/middleware/wopicontext.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,9 @@ func WopiContextAuthMiddleware(cfg *config.Config, st microstore.Store, next htt
9191

9292
claims := &Claims{}
9393
_, err := jwt.ParseWithClaims(accessToken, claims, func(token *jwt.Token) (interface{}, error) {
94-
95-
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
94+
if token.Method != jwt.SigningMethodHS256 {
9695
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
9796
}
98-
9997
return []byte(cfg.Wopi.Secret), nil
10098
})
10199

@@ -198,8 +196,13 @@ func GenerateWopiToken(wopiContext WopiContext, cfg *config.Config, st microstor
198196
}
199197

200198
cs3Claims := &jwt.RegisteredClaims{}
201-
cs3JWTparser := jwt.Parser{}
202-
_, _, err = cs3JWTparser.ParseUnverified(wopiContext.AccessToken, cs3Claims)
199+
_, err = jwt.ParseWithClaims(wopiContext.AccessToken, cs3Claims, func(token *jwt.Token) (interface{}, error) {
200+
if token.Method != jwt.SigningMethodHS256 {
201+
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
202+
}
203+
204+
return []byte(cfg.TokenManager.JWTSecret), nil
205+
})
203206
if err != nil {
204207
return "", 0, err
205208
}
@@ -243,7 +246,10 @@ func parseWopiFileID(cfg *config.Config, path string) string {
243246
}
244247
// check if the fileid is a jwt
245248
if strings.Contains(s[3], ".") {
246-
token, err := jwt.Parse(s[3], func(_ *jwt.Token) (interface{}, error) {
249+
token, err := jwt.Parse(s[3], func(token *jwt.Token) (interface{}, error) {
250+
if token.Method != jwt.SigningMethodHS256 {
251+
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
252+
}
247253
return []byte(cfg.Wopi.ProxySecret), nil
248254
})
249255
if err != nil {

services/collaboration/pkg/middleware/wopicontext_test.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,12 @@ var _ = Describe("Wopi Context Middleware", func() {
130130
AccessToken: token,
131131
}
132132
// use wrong wopi secret when generating the wopi token
133-
wopiToken, ttl, err := middleware.GenerateWopiToken(wopiContext, &config.Config{Wopi: config.Wopi{
134-
Secret: "wrongSecret",
135-
}}, nil)
133+
wopiToken, ttl, err := middleware.GenerateWopiToken(wopiContext, &config.Config{
134+
TokenManager: &config.TokenManager{JWTSecret: cfg.TokenManager.JWTSecret},
135+
Wopi: config.Wopi{
136+
Secret: "wrongSecret",
137+
},
138+
}, nil)
136139
q := req.URL.Query()
137140
q.Add("access_token", wopiToken)
138141
q.Add("access_token_ttl", strconv.FormatInt(ttl, 10))

services/collaboration/pkg/service/grpc/v0/service_test.go

+10-6
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ var _ = Describe("Discovery", func() {
7474
)
7575

7676
BeforeEach(func() {
77-
cfg = &config.Config{}
77+
cfg = &config.Config{
78+
TokenManager: &config.TokenManager{
79+
JWTSecret: "topsecret",
80+
},
81+
}
7882
gatewayClient = &cs3mocks.GatewayAPIClient{}
7983

8084
gatewaySelector := mocks.NewSelectable[gatewayv1beta1.GatewayAPIClient](GinkgoT())
@@ -147,7 +151,7 @@ var _ = Describe("Discovery", func() {
147151
nowTime := time.Now()
148152

149153
cfg.Wopi.WopiSrc = "https://wopiserver.test.prv"
150-
cfg.Wopi.Secret = "my_supa_secret"
154+
cfg.Wopi.Secret = "topsecret"
151155
cfg.Wopi.DisableChat = disableChat
152156
cfg.App.Name = appName
153157
cfg.App.Product = appName
@@ -213,7 +217,7 @@ var _ = Describe("Discovery", func() {
213217
nowTime := time.Now()
214218

215219
cfg.Wopi.WopiSrc = "https://wopiserver.test.prv"
216-
cfg.Wopi.Secret = "my_supa_secret"
220+
cfg.Wopi.Secret = "topsecret"
217221
cfg.Wopi.ProxyURL = "https://office.proxy.test.prv"
218222
cfg.Wopi.ProxySecret = "your_supa_secret"
219223
cfg.App.Name = "Microsoft"
@@ -258,7 +262,7 @@ var _ = Describe("Discovery", func() {
258262
nowTime := time.Now()
259263

260264
cfg.Wopi.WopiSrc = "htttps://wopiserver.test.prv"
261-
cfg.Wopi.Secret = "my_supa_secret"
265+
cfg.Wopi.Secret = "topsecret"
262266
cfg.App.Name = "Microsoft"
263267

264268
myself := &userv1beta1.User{
@@ -299,7 +303,7 @@ var _ = Describe("Discovery", func() {
299303
nowTime := time.Now()
300304

301305
cfg.Wopi.WopiSrc = "htttps://wopiserver.test.prv"
302-
cfg.Wopi.Secret = "my_supa_secret"
306+
cfg.Wopi.Secret = "topsecret"
303307
cfg.App.Name = "Microsoft"
304308

305309
myself := &userv1beta1.User{
@@ -341,7 +345,7 @@ var _ = Describe("Discovery", func() {
341345
nowTime := time.Now()
342346

343347
cfg.Wopi.WopiSrc = "htttps://wopiserver.test.prv"
344-
cfg.Wopi.Secret = "my_supa_secret"
348+
cfg.Wopi.Secret = "topsecret"
345349
cfg.App.Name = "OnlyOffice"
346350
cfg.App.Product = "OnlyOffice"
347351

services/thumbnails/pkg/service/http/v0/service.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,9 @@ func (s Thumbnails) TransferTokenValidator(next http.Handler) http.Handler {
119119
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
120120
logger := s.logger.SubloggerWithRequestID(r.Context())
121121
tokenString := r.Header.Get("Transfer-Token")
122-
token, err := jwt.ParseWithClaims(tokenString, &tjwt.ThumbnailClaims{}, func(token *jwt.Token) (interface{}, error) {
123-
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
122+
claims := &tjwt.ThumbnailClaims{}
123+
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
124+
if token.Method != jwt.SigningMethodHS256 {
124125
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
125126
}
126127
return []byte(s.config.Thumbnail.TransferSecret), nil

0 commit comments

Comments
 (0)