Files
opencloud/services/groupware/pkg/groupware/reva.go
Pascal Bleser a26e7f483c groupware: add OIDC authentication support between Groupware backend and Stalwart
* re-implement the auth-api service to authenticate Reva tokens
   following the OIDC Userinfo endpoint specification

 * pass the context where necessary and add an authenticator interface
   to the JMAP HTTP driver, in order to select between master
   authentication (which is used when GROUPWARE_JMAP_MASTER_USERNAME and
   GROUPWARE_JMAP_MASTER_PASSWORD are both set) and OIDC token
   forwarding through bearer auth

 * add Stalwart directory configuration "idmoidc" which uses the
   OpenCloud auth-api service API (/auth/) to validate the token it
   received as bearer auth from the Groupware backend's JMAP client,
   using it as an OIDC Userinfo endpoint

 * implement optional additional shared secret to secure the Userinfo
   service, as an additional path parameter
2026-07-01 14:57:30 +02:00

115 lines
2.8 KiB
Go

package groupware
import (
"context"
"errors"
"net/http"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/opencloud-eu/opencloud/pkg/jmap"
"github.com/opencloud-eu/opencloud/pkg/log"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
)
// UsernameProvider implementation that uses Reva's enrichment of the Context
// to retrieve the current username.
type revaContextUsernameProvider struct {
}
var _ userProvider = revaContextUsernameProvider{}
func newRevaContextUsernameProvider() userProvider {
return revaContextUsernameProvider{}
}
var (
errUserNotInRevaContext = errors.New("failed to find user in reva context")
)
func (r revaContextUsernameProvider) GetUser(req *http.Request, ctx context.Context, logger *log.Logger) (user, error) {
u, ok := revactx.ContextGetUser(ctx)
if !ok {
err := errUserNotInRevaContext
logger.Error().Err(err).Ctx(ctx).Msgf("could not get user: user not in reva context: %v", ctx)
return nil, err
}
return revaUser{user: u}, nil
}
type revaUser struct {
user *userv1beta1.User
}
func (r revaUser) GetUsername() string {
return r.user.GetUsername()
}
func (r revaUser) GetId() string {
return r.user.GetId().GetOpaqueId()
}
var _ user = revaUser{}
type RevaBearerHttpJmapClientAuthenticator struct {
}
func newRevaBearerHttpJmapClientAuthenticator() jmap.HttpJmapClientAuthenticator {
return &RevaBearerHttpJmapClientAuthenticator{}
}
var _ jmap.HttpJmapClientAuthenticator = &RevaBearerHttpJmapClientAuthenticator{}
type RevaError struct {
code int
err error
}
var _ jmap.Error = &RevaError{}
func (e RevaError) Code() int {
return e.code
}
func (e RevaError) Unwrap() error {
return e.err
}
func (e RevaError) Error() string {
if e.err != nil {
return e.err.Error()
} else {
return ""
}
}
const (
revaErrorTokenMissingInRevaContext = iota + 10000
)
var tokenMissingInRevaContext = RevaError{
code: revaErrorTokenMissingInRevaContext,
err: errors.New("Token is missing from Reva context"),
}
func (h *RevaBearerHttpJmapClientAuthenticator) Authenticate(ctx context.Context, username string, logger *log.Logger, req *http.Request) jmap.Error {
token, ok := revactx.ContextGetToken(ctx)
if !ok {
err := tokenMissingInRevaContext
logger.Error().Err(err).Ctx(ctx).Msgf("could not get token: token not in reva context: %v", ctx)
return err
} else {
req.Header.Add("Authorization", "Bearer "+token)
return nil
}
}
func (h *RevaBearerHttpJmapClientAuthenticator) AuthenticateWS(ctx context.Context, username string, logger *log.Logger, headers http.Header) jmap.Error {
token, ok := revactx.ContextGetToken(ctx)
if !ok {
err := tokenMissingInRevaContext
logger.Error().Err(err).Ctx(ctx).Msgf("could not get token: token not in reva context: %v", ctx)
return err
} else {
headers.Add("Authorization", "Bearer "+token)
return nil
}
}