Files
opencloud/services/proxy/pkg/middleware/public_share_auth.go

130 lines
4.4 KiB
Go

package middleware
import (
"net/http"
"strings"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/opencloud-eu/opencloud/pkg/log"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
)
const (
headerRevaAccessToken = revactx.TokenHeader
headerShareToken = "public-token"
basicAuthPasswordPrefix = "password|"
authenticationType = "publicshares"
_paramSignature = "signature"
_paramExpiration = "expiration"
)
// PublicShareAuthenticator is the authenticator which can authenticate public share requests.
// It will add the share owner into the request context.
type PublicShareAuthenticator struct {
Logger log.Logger
RevaGatewaySelector pool.Selectable[gateway.GatewayAPIClient]
}
// The archiver is able to create archives from public shares in which case it needs to use the
// PublicShareAuthenticator. It might however also be called using "normal" authentication or
// using signed url, which are handled by other middleware. For this reason we can't just
// handle `/archiver` with the `isPublicPath()` check.
func isPublicShareArchive(r *http.Request) bool {
if strings.HasPrefix(r.URL.Path, "/archiver") {
if r.URL.Query().Get(headerShareToken) != "" || r.Header.Get(headerShareToken) != "" {
return true
}
}
return false
}
// The app open requests can be made in public share contexts. For that the PublicShareAuthenticator needs to
// augment the request context.
// The app open requests can also be made in authenticated context. In these cases the PublicShareAuthenticator
// needs to ignore the request.
func isPublicShareAppOpen(r *http.Request) bool {
return (strings.HasPrefix(r.URL.Path, "/app/open") || strings.HasPrefix(r.URL.Path, "/app/new")) &&
(r.URL.Query().Get(headerShareToken) != "" || r.Header.Get(headerShareToken) != "")
}
// The public-files requests can also be made in authenticated context. In these cases the OIDCAuthenticator and
// the BasicAuthenticator needs to ignore the request when the headerShareToken exist.
func isPublicWithShareToken(r *http.Request) bool {
return (strings.HasPrefix(r.URL.Path, "/dav/public-files") || strings.HasPrefix(r.URL.Path, "/remote.php/dav/public-files")) &&
(r.URL.Query().Get(headerShareToken) != "" || r.Header.Get(headerShareToken) != "")
}
// Authenticate implements the authenticator interface to authenticate requests via public share auth.
func (a PublicShareAuthenticator) Authenticate(r *http.Request) (*http.Request, bool) {
if !isPublicPath(r.URL.Path) && !isPublicShareArchive(r) && !isPublicShareAppOpen(r) {
return nil, false
}
query := r.URL.Query()
shareToken := r.Header.Get(headerShareToken)
if shareToken == "" {
shareToken = query.Get(headerShareToken)
}
if shareToken == "" {
// If the share token is not set then we don't need to inject the user to
// the request context so we can just continue with the request.
return r, true
}
var sharePassword string
if signature := query.Get(_paramSignature); signature != "" {
expiration := query.Get(_paramExpiration)
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
}
}
client, err := a.RevaGatewaySelector.Next()
if err != nil {
a.Logger.Error().
Err(err).
Str("authenticator", "public_share").
Str("public_share_token", shareToken).
Str("path", r.URL.Path).
Msg("could not select next gateway client")
return nil, false
}
authResp, err := client.Authenticate(r.Context(), &gateway.AuthenticateRequest{
Type: authenticationType,
ClientId: shareToken,
ClientSecret: sharePassword,
})
if err != nil {
a.Logger.Error().
Err(err).
Str("authenticator", "public_share").
Str("public_share_token", shareToken).
Str("path", r.URL.Path).
Msg("failed to authenticate request")
return nil, false
}
r.Header.Add(headerRevaAccessToken, authResp.Token)
a.Logger.Debug().
Str("authenticator", "public_share").
Str("path", r.URL.Path).
Msg("successfully authenticated request")
return r, true
}