feat(auth-app): list and delete endpoints

Signed-off-by: jkoberg <jkoberg@owncloud.com>
This commit is contained in:
jkoberg
2024-08-07 10:47:20 +02:00
parent 48af3fae32
commit 2a498daf07
3 changed files with 200 additions and 8 deletions

View File

@@ -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, "/")
}
}

View File

@@ -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
}
}

View File

@@ -260,7 +260,7 @@ func DefaultPolicies() []config.Policy {
},
{
Endpoint: "/auth-app/tokens",
Service: "com.owncloud.api.auth-app",
Service: "com.owncloud.web.auth-app",
},
},
},