From 2a498daf073b0e511ae9cf3f45d3169fe5115047 Mon Sep 17 00:00:00 2001 From: jkoberg Date: Wed, 7 Aug 2024 10:47:20 +0200 Subject: [PATCH] feat(auth-app): list and delete endpoints Signed-off-by: jkoberg --- .../pkg/config/defaults/defaultconfig.go | 17 +- services/auth-app/pkg/service/service.go | 189 +++++++++++++++++- .../pkg/config/defaults/defaultconfig.go | 2 +- 3 files changed, 200 insertions(+), 8 deletions(-) diff --git a/services/auth-app/pkg/config/defaults/defaultconfig.go b/services/auth-app/pkg/config/defaults/defaultconfig.go index cd845f7f4d..66b362f8ba 100644 --- a/services/auth-app/pkg/config/defaults/defaultconfig.go +++ b/services/auth-app/pkg/config/defaults/defaultconfig.go @@ -1,6 +1,8 @@ package defaults import ( + "strings" + "github.com/owncloud/ocis/v2/ocis-pkg/shared" "github.com/owncloud/ocis/v2/ocis-pkg/structs" "github.com/owncloud/ocis/v2/services/auth-app/pkg/config" @@ -29,8 +31,8 @@ func DefaultConfig() *config.Config { Protocol: "tcp", }, HTTP: config.HTTP{ - Addr: "127.0.0.1:0", - Namespace: "com.owncloud.api", + Addr: "127.0.0.1:9247", + Namespace: "com.owncloud.web", Root: "/", CORS: config.CORS{ AllowedOrigins: []string{"*"}, @@ -90,9 +92,16 @@ func EnsureDefaults(cfg *config.Config) { if cfg.GRPC.TLS == nil && cfg.Commons != nil { cfg.GRPC.TLS = structs.CopyOrZeroValue(cfg.Commons.GRPCServiceTLS) } + + if cfg.Commons != nil { + cfg.HTTP.TLS = cfg.Commons.HTTPServiceTLS + } } // Sanitize sanitized the configuration -func Sanitize(_ *config.Config) { - // nothing to sanitize here atm +func Sanitize(cfg *config.Config) { + // sanitize config + if cfg.HTTP.Root != "/" { + cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/") + } } diff --git a/services/auth-app/pkg/service/service.go b/services/auth-app/pkg/service/service.go index 37e67974b8..db16ababa7 100644 --- a/services/auth-app/pkg/service/service.go +++ b/services/auth-app/pkg/service/service.go @@ -1,16 +1,31 @@ package service import ( - "fmt" + "context" + "encoding/json" + "errors" "net/http" + "time" + applications "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/v2/pkg/auth/scope" + ctxpkg "github.com/cs3org/reva/v2/pkg/ctx" "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" "github.com/go-chi/chi/v5" + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/auth-app/pkg/config" + "google.golang.org/grpc/metadata" ) // AuthAppService defines the service interface. type AuthAppService struct { + log log.Logger + cfg *config.Config gws pool.Selectable[gateway.GatewayAPIClient] m *chi.Mux } @@ -22,12 +37,16 @@ func NewAuthAppService(opts ...Option) (*AuthAppService, error) { opt(o) } a := &AuthAppService{ + log: o.Logger, + cfg: o.Config, gws: o.GatewaySelector, m: o.Mux, } a.m.Route("/auth-app/tokens", func(r chi.Router) { + r.Get("/", a.HandleList) r.Post("/", a.HandleCreate) + r.Delete("/", a.HandleDelete) }) return a, nil @@ -38,7 +57,171 @@ func (a *AuthAppService) ServeHTTP(w http.ResponseWriter, r *http.Request) { a.m.ServeHTTP(w, r) } -// HandleCreate handles the creation of a new auth-token +// HandleCreate handles the creation of app tokens func (a *AuthAppService) HandleCreate(w http.ResponseWriter, r *http.Request) { - fmt.Println("ALIVE") + gwc, err := a.gws.Next() + if err != nil { + http.Error(w, "error getting gateway client", http.StatusInternalServerError) + return + } + + ctx := getContext(r) + + q := r.URL.Query() + cid := buildClientID(q.Get("userID"), q.Get("userName")) + if cid != "" { + ctx, err = a.authenticateUser(cid, gwc) + if err != nil { + a.log.Error().Err(err).Msg("error authenticating user") + http.Error(w, "error authenticating user", http.StatusInternalServerError) + return + } + } + + scopes, err := scope.AddOwnerScope(map[string]*authpb.Scope{}) + if err != nil { + a.log.Error().Err(err).Msg("error adding owner scope") + http.Error(w, "error adding owner scope", http.StatusInternalServerError) + return + } + + res, err := gwc.GenerateAppPassword(ctx, &applications.GenerateAppPasswordRequest{ + TokenScope: scopes, + Label: "Generated via API", + Expiration: &types.Timestamp{ + Seconds: uint64(time.Now().Add(time.Hour).Unix()), + }, + }) + if err != nil { + a.log.Error().Err(err).Msg("error generating app password") + http.Error(w, "error generating app password", http.StatusInternalServerError) + return + } + if res.GetStatus().GetCode() != rpc.Code_CODE_OK { + a.log.Error().Str("status", res.GetStatus().GetCode().String()).Msg("error generating app password") + http.Error(w, "error generating app password: "+res.GetStatus().GetMessage(), http.StatusInternalServerError) + return + } + + b, err := json.Marshal(res.GetAppPassword()) + if err != nil { + a.log.Error().Err(err).Msg("error marshaling app password") + http.Error(w, "error marshaling app password", http.StatusInternalServerError) + return + } + + if _, err := w.Write(b); err != nil { + a.log.Error().Err(err).Msg("error writing response") + http.Error(w, "error writing response", http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) +} + +// HandleList handles listing of app tokens +func (a *AuthAppService) HandleList(w http.ResponseWriter, r *http.Request) { + gwc, err := a.gws.Next() + if err != nil { + a.log.Error().Err(err).Msg("error getting gateway client") + http.Error(w, "error getting gateway client", http.StatusInternalServerError) + return + } + + ctx := getContext(r) + + res, err := gwc.ListAppPasswords(ctx, &applications.ListAppPasswordsRequest{}) + if err != nil { + a.log.Error().Err(err).Msg("error listing app passwords") + http.Error(w, "error listing app passwords", http.StatusInternalServerError) + return + } + + if res.GetStatus().GetCode() != rpc.Code_CODE_OK { + a.log.Error().Str("status", res.GetStatus().GetCode().String()).Msg("error listing app passwords") + http.Error(w, "error listing app passwords: "+res.GetStatus().GetMessage(), http.StatusInternalServerError) + return + } + + b, err := json.Marshal(res.GetAppPasswords()) + if err != nil { + a.log.Error().Err(err).Msg("error marshaling app passwords") + http.Error(w, "error marshaling app passwords", http.StatusInternalServerError) + return + } + + if _, err := w.Write(b); err != nil { + a.log.Error().Err(err).Msg("error writing response") + http.Error(w, "error writing response", http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusOK) +} + +// HandleDelete handles deletion of app tokens +func (a *AuthAppService) HandleDelete(w http.ResponseWriter, r *http.Request) { + gwc, err := a.gws.Next() + if err != nil { + a.log.Error().Err(err).Msg("error getting gateway client") + http.Error(w, "error getting gateway client", http.StatusInternalServerError) + return + } + + ctx := getContext(r) + + pw := r.URL.Query().Get("token") + if pw == "" { + a.log.Info().Msg("missing token") + http.Error(w, "missing token", http.StatusBadRequest) + return + } + + res, err := gwc.InvalidateAppPassword(ctx, &applications.InvalidateAppPasswordRequest{Password: pw}) + if err != nil { + a.log.Error().Err(err).Msg("error invalidating app password") + http.Error(w, "error invalidating app password", http.StatusInternalServerError) + return + } + + if res.GetStatus().GetCode() != rpc.Code_CODE_OK { + a.log.Error().Str("status", res.GetStatus().GetCode().String()).Msg("error invalidating app password") + http.Error(w, "error invalidating app password: "+res.GetStatus().GetMessage(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + +func (a *AuthAppService) authenticateUser(clientID string, gwc gateway.GatewayAPIClient) (context.Context, error) { + ctx := context.Background() + authRes, err := gwc.Authenticate(ctx, &gateway.AuthenticateRequest{ + Type: "machine", + ClientId: clientID, + ClientSecret: a.cfg.MachineAuthAPIKey, + }) + if err != nil { + return nil, err + } + + if authRes.GetStatus().GetCode() != rpc.Code_CODE_OK { + return nil, errors.New("error authenticating user: " + authRes.GetStatus().GetMessage()) + } + + ctx = ctxpkg.ContextSetUser(ctx, &userpb.User{Id: authRes.GetUser().GetId()}) + return metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, authRes.GetToken()), nil +} + +func getContext(r *http.Request) context.Context { + ctx := r.Context() + return metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, r.Header.Get("X-Access-Token")) +} + +func buildClientID(userID, userName string) string { + switch { + default: + return "" + case userID != "": + return "userid:" + userID + case userName != "": + return "username:" + userName + } } diff --git a/services/proxy/pkg/config/defaults/defaultconfig.go b/services/proxy/pkg/config/defaults/defaultconfig.go index f59255b396..40253045e5 100644 --- a/services/proxy/pkg/config/defaults/defaultconfig.go +++ b/services/proxy/pkg/config/defaults/defaultconfig.go @@ -260,7 +260,7 @@ func DefaultPolicies() []config.Policy { }, { Endpoint: "/auth-app/tokens", - Service: "com.owncloud.api.auth-app", + Service: "com.owncloud.web.auth-app", }, }, },