Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -0,0 +1,95 @@
package middleware
import (
"net/http"
gmmetadata "go-micro.dev/v4/metadata"
"google.golang.org/grpc/metadata"
"github.com/qsfera/server/pkg/account"
"github.com/qsfera/server/pkg/log"
opkgm "github.com/qsfera/server/pkg/middleware"
"github.com/qsfera/server/services/graph/pkg/errorcode"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/token/manager/jwt"
)
// authOptions initializes the available default options.
func authOptions(opts ...account.Option) account.Options {
opt := account.Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Auth provides a middleware to authenticate requests using the x-access-token header value
// and write it to the context. If there is no x-access-token the middleware prevents access and renders a json document.
func Auth(opts ...account.Option) func(http.Handler) http.Handler {
// Note: This largely duplicates what pkg/middleware/account.go already does (apart from a slightly different error
// handling). Ideally we should merge both middlewares.
opt := authOptions(opts...)
tokenManager, err := jwt.New(map[string]any{
"secret": opt.JWTSecret,
"expires": int64(24 * 60 * 60),
})
if err != nil {
opt.Logger.Fatal().Err(err).Msgf("Could not initialize token-manager")
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
t := r.Header.Get(revactx.TokenHeader)
if t == "" {
errorcode.InvalidAuthenticationToken.Render(w, r, http.StatusUnauthorized, "Access token is empty.")
/* msgraph error for GET https://graph.microsoft.com/v1.0/me
{
"error":
{
"code":"InvalidAuthenticationToken",
"message":"Access token is empty.",
"innerError":{
"date":"2021-07-09T14:40:51",
"request-id":"bb12f7db-b4c4-43a9-ba4b-31676aeed019",
"client-request-id":"bb12f7db-b4c4-43a9-ba4b-31676aeed019"
}
}
}
*/
return
}
u, tokenScope, err := tokenManager.DismantleToken(r.Context(), t)
if err != nil {
errorcode.InvalidAuthenticationToken.Render(w, r, http.StatusUnauthorized, "invalid token")
return
}
if ok, err := scope.VerifyScope(ctx, tokenScope, r); err != nil || !ok {
opt.Logger.Error().Str(log.RequestIDString, r.Header.Get("X-Request-ID")).Err(err).Msg("verifying scope failed")
errorcode.InvalidAuthenticationToken.Render(w, r, http.StatusUnauthorized, "verifying scope failed")
return
}
ctx = revactx.ContextSetToken(ctx, t)
ctx = revactx.ContextSetUser(ctx, u)
ctx = gmmetadata.Set(ctx, opkgm.AccountID, u.GetId().GetOpaqueId())
if m := u.GetOpaque().GetMap(); m != nil {
if roles, ok := m["roles"]; ok {
ctx = gmmetadata.Set(ctx, opkgm.RoleIDs, string(roles.GetValue()))
}
}
ctx = metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, t)
initiatorID := r.Header.Get(revactx.InitiatorHeader)
if initiatorID != "" {
ctx = revactx.ContextSetInitiator(ctx, initiatorID)
ctx = metadata.AppendToOutgoingContext(ctx, revactx.InitiatorHeader, initiatorID)
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
@@ -0,0 +1,54 @@
package middleware
import (
"net/http"
"github.com/qsfera/server/pkg/log"
"github.com/qsfera/server/pkg/roles"
"github.com/qsfera/server/services/graph/pkg/errorcode"
settings "github.com/qsfera/server/services/settings/pkg/service/v0"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
)
// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions
func RequireAdmin(rm *roles.Manager, logger log.Logger) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
l := logger.With().Str("middleware", "requireAdmin").Logger()
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u, ok := revactx.ContextGetUser(r.Context())
if !ok {
errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized")
return
}
if u.Id == nil || u.Id.OpaqueId == "" {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "user is missing an id")
return
}
// get roles from context
roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context())
if !ok {
l.Debug().Str("userid", u.Id.OpaqueId).Msg("No roles in context, contacting settings service")
var err error
roleIDs, err = rm.FindRoleIDsForUser(r.Context(), u.Id.OpaqueId)
if err != nil {
l.Error().Err(err).Str("userid", u.Id.OpaqueId).Msg("Failed to get roles for user")
errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized")
return
}
if len(roleIDs) == 0 {
l.Error().Err(err).Str("userid", u.Id.OpaqueId).Msg("No roles assigned to user")
errorcode.AccessDenied.Render(w, r, http.StatusUnauthorized, "Unauthorized")
return
}
}
// check if permission is present in roles of the authenticated account
if rm.FindPermissionByID(r.Context(), roleIDs, settings.AccountManagementPermissionID) != nil {
next.ServeHTTP(w, r)
return
}
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "Forbidden")
})
}
}
@@ -0,0 +1,45 @@
package middleware
import (
"crypto/sha256"
"crypto/subtle"
"net/http"
"github.com/qsfera/server/services/graph/pkg/errorcode"
)
var (
// ErrInvalidToken is returned when the request token is invalid.
ErrInvalidToken = "invalid or missing token"
)
// Token provides a middleware to check access secured by a static token.
func Token(token string) func(http.Handler) http.Handler {
h := sha256.New()
requiredTokenHash := h.Sum(([]byte("Bearer " + token)))
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if token == "" {
next.ServeHTTP(w, r)
return
}
header := r.Header.Get("Authorization")
if header == "" {
errorcode.InvalidAuthenticationToken.Render(w, r, http.StatusUnauthorized, ErrInvalidToken)
return
}
h = sha256.New()
providedTokenHash := h.Sum([]byte(header))
if subtle.ConstantTimeCompare(requiredTokenHash, providedTokenHash) == 0 {
errorcode.InvalidAuthenticationToken.Render(w, r, http.StatusUnauthorized, ErrInvalidToken)
return
}
next.ServeHTTP(w, r)
})
}
}
@@ -0,0 +1,49 @@
package middleware
import (
"net/http"
"net/http/httptest"
"testing"
)
type dummyHandler struct{}
func (h dummyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func TestToken(t *testing.T) {
dh := dummyHandler{}
handler := Token("test-api-key")(dh)
req, err := http.NewRequest("GET", "/token-protected", nil)
req.Header.Set("Authorization", "Bearer wrong")
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusUnauthorized {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusUnauthorized)
}
req, err = http.NewRequest("GET", "/token-protected", nil)
req.Header.Set("Authorization", "Bearer test-api-key")
if err != nil {
t.Fatal(err)
}
rr = httptest.NewRecorder()
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
}