feat: use short tokens as access tokens

The "real" access token will be stored using the short token as key.
This short token will be sent to the clients to be used as access token
for the WOPI server.

This is configurable, and requires a store in order to keep the tokens.
This commit is contained in:
Juan Pablo Villafáñez
2024-10-24 08:46:54 +02:00
parent 4dfba210e1
commit b8f8ca813e
14 changed files with 127 additions and 15 deletions
@@ -2,10 +2,13 @@ package middleware
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"net/http"
"strings"
"time"
appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1"
providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
@@ -15,6 +18,7 @@ import (
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/helpers"
"github.com/rs/zerolog"
microstore "go-micro.dev/v4/store"
"google.golang.org/grpc/metadata"
)
@@ -44,7 +48,7 @@ type WopiContext struct {
// * The created WopiContext for the request
// * A contextual zerologger containing information about the request
// and the WopiContext
func WopiContextAuthMiddleware(cfg *config.Config, next http.Handler) http.Handler {
func WopiContextAuthMiddleware(cfg *config.Config, st microstore.Store, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
@@ -67,6 +71,23 @@ func WopiContextAuthMiddleware(cfg *config.Config, next http.Handler) http.Handl
return
}
if cfg.Wopi.ShortTokens {
records, err := st.Read(accessToken)
if err != nil {
wopiLogger.Error().Err(err).Msg("cannot retrieve access token from store")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
if len(records) != 1 {
wopiLogger.Error().Int("records", len(records)).Msg("no record found for the token")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
accessToken = string(records[0].Value)
}
claims := &Claims{}
_, err := jwt.ParseWithClaims(accessToken, claims, func(token *jwt.Token) (interface{}, error) {
@@ -163,7 +184,11 @@ func WopiContextToCtx(ctx context.Context, wopiContext WopiContext) context.Cont
// The access token inside the wopiContext is expected to be decrypted.
// In order to generate the access token for WOPI, the reva token inside the
// wopiContext will be encrypted
func GenerateWopiToken(wopiContext WopiContext, cfg *config.Config) (string, int64, error) {
func GenerateWopiToken(wopiContext WopiContext, cfg *config.Config, st microstore.Store) (string, int64, error) {
if cfg.Wopi.ShortTokens && st == nil {
return "", 0, errors.New("Cannot generate a short token without microstore")
}
cryptedReqAccessToken, err := EncryptAES([]byte(cfg.Wopi.Secret), wopiContext.AccessToken)
if err != nil {
return "", 0, err
@@ -187,6 +212,20 @@ func GenerateWopiToken(wopiContext WopiContext, cfg *config.Config) (string, int
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
accessToken, err := token.SignedString([]byte(cfg.Wopi.Secret))
if cfg.Wopi.ShortTokens {
c := sha256.New()
c.Write([]byte(accessToken))
shortAccessToken := hex.EncodeToString(c.Sum(nil))
errWrite := st.Write(&microstore.Record{
Key: shortAccessToken,
Value: []byte(accessToken),
Expiry: claims.ExpiresAt.Sub(time.Now()),
})
return shortAccessToken, claims.ExpiresAt.UnixMilli(), errWrite
}
return accessToken, claims.ExpiresAt.UnixMilli(), err
}