rewrite the auth middleware

The old approach of the authentication middlewares had the problem that when an authenticator could not authenticate a request it would still send it to the next handler, in case that the next one can authenticate it. But if no authenticator could successfully authenticate the request, it would still be handled, which leads to unauthorized access.
This commit is contained in:
David Christofas
2022-08-12 10:47:43 +02:00
parent 02adcbd92a
commit e96819bce8
8 changed files with 422 additions and 393 deletions
@@ -5,6 +5,7 @@ import (
"strings"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
)
const (
@@ -14,59 +15,58 @@ const (
authenticationType = "publicshares"
)
// PublicShareAuth ...
func PublicShareAuth(opts ...Option) func(next http.Handler) http.Handler {
options := newOptions(opts...)
logger := options.Logger
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
shareToken := r.Header.Get(headerShareToken)
if shareToken == "" {
shareToken = r.URL.Query().Get(headerShareToken)
}
// Currently we only want to authenticate app open request coming from public shares.
if shareToken == "" {
// Don't authenticate
next.ServeHTTP(w, r)
return
}
var sharePassword string
if signature := r.URL.Query().Get("signature"); signature != "" {
expiration := r.URL.Query().Get("expiration")
if expiration == "" {
logger.Warn().Str("signature", signature).Msg("cannot do signature auth without the expiration")
next.ServeHTTP(w, r)
return
}
sharePassword = strings.Join([]string{"signature", signature, expiration}, "|")
} else {
// We can ignore the username since it is always set to "public" in public shares.
_, password, ok := r.BasicAuth()
sharePassword = basicAuthPasswordPrefix
if ok {
sharePassword += password
}
}
authResp, err := options.RevaGatewayClient.Authenticate(r.Context(), &gateway.AuthenticateRequest{
Type: authenticationType,
ClientId: shareToken,
ClientSecret: sharePassword,
})
if err != nil {
logger.Debug().Err(err).Str("public_share_token", shareToken).Msg("could not authenticate public share")
// try another middleware
next.ServeHTTP(w, r)
return
}
r.Header.Add(headerRevaAccessToken, authResp.Token)
next.ServeHTTP(w, r)
})
type PublicShareAuthenticator struct {
Logger log.Logger
RevaGatewayClient gateway.GatewayAPIClient
}
func (a PublicShareAuthenticator) Authenticate(r *http.Request) (*http.Request, bool) {
if !isPublicPath(r.URL.Path) {
return nil, false
}
query := r.URL.Query()
shareToken := r.Header.Get(headerShareToken)
if shareToken == "" {
shareToken = query.Get(headerShareToken)
}
// Currently we only want to authenticate app open request coming from public shares.
if shareToken == "" {
// Don't authenticate
return nil, false
}
var sharePassword string
if signature := query.Get("signature"); signature != "" {
expiration := query.Get("expiration")
if expiration == "" {
a.Logger.Warn().Str("signature", signature).Msg("cannot do signature auth without the expiration")
return nil, false
}
sharePassword = strings.Join([]string{"signature", signature, expiration}, "|")
} else {
// We can ignore the username since it is always set to "public" in public shares.
_, password, ok := r.BasicAuth()
sharePassword = basicAuthPasswordPrefix
if ok {
sharePassword += password
}
}
authResp, err := a.RevaGatewayClient.Authenticate(r.Context(), &gateway.AuthenticateRequest{
Type: authenticationType,
ClientId: shareToken,
ClientSecret: sharePassword,
})
if err != nil {
a.Logger.Debug().Err(err).Str("public_share_token", shareToken).Msg("could not authenticate public share")
// try another middleware
return nil, false
}
r.Header.Add(headerRevaAccessToken, authResp.Token)
return r, false
}