feat(auth-app): secure create endpoint

Signed-off-by: jkoberg <jkoberg@owncloud.com>
This commit is contained in:
jkoberg
2024-08-07 13:01:32 +02:00
parent 2a498daf07
commit d483234b65
7 changed files with 116 additions and 4 deletions
+9
View File
@@ -7,6 +7,7 @@ import (
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/go-chi/chi/v5"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
"go.opentelemetry.io/otel/trace"
)
@@ -22,6 +23,7 @@ type Options struct {
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
Mux *chi.Mux
TracerProvider trace.TracerProvider
RoleClient settingssvc.RoleService
}
// Logger provides a function to set the logger option.
@@ -65,3 +67,10 @@ func Mux(m *chi.Mux) Option {
o.Mux = m
}
}
// RoleClient adds a grpc client for the role service
func RoleClient(rs settingssvc.RoleService) Option {
return func(o *Options) {
o.RoleClient = rs
}
}
+68 -4
View File
@@ -12,13 +12,16 @@ import (
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/appctx"
"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/cs3org/reva/v2/pkg/utils"
"github.com/go-chi/chi/v5"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/ocis-pkg/roles"
"github.com/owncloud/ocis/v2/services/auth-app/pkg/config"
settings "github.com/owncloud/ocis/v2/services/settings/pkg/service/v0"
"google.golang.org/grpc/metadata"
)
@@ -28,6 +31,7 @@ type AuthAppService struct {
cfg *config.Config
gws pool.Selectable[gateway.GatewayAPIClient]
m *chi.Mux
r *roles.Manager
}
// NewAuthAppService initializes a new AuthAppService.
@@ -36,11 +40,19 @@ func NewAuthAppService(opts ...Option) (*AuthAppService, error) {
for _, opt := range opts {
opt(o)
}
r := roles.NewManager(
// TODO: caching?
roles.Logger(o.Logger),
roles.RoleService(o.RoleClient),
)
a := &AuthAppService{
log: o.Logger,
cfg: o.Config,
gws: o.GatewaySelector,
m: o.Mux,
r: &r,
}
a.m.Route("/auth-app/tokens", func(r chi.Router) {
@@ -68,8 +80,31 @@ func (a *AuthAppService) HandleCreate(w http.ResponseWriter, r *http.Request) {
ctx := getContext(r)
q := r.URL.Query()
expiry, err := time.ParseDuration(q.Get("expiry"))
if err != nil {
a.log.Info().Err(err).Msg("error parsing expiry")
http.Error(w, "error parsing expiry. Use e.g. 30m or 72h", http.StatusBadRequest)
return
}
cid := buildClientID(q.Get("userID"), q.Get("userName"))
if cid != "" {
if !a.cfg.AllowImpersonation {
a.log.Error().Msg("impersonation is not allowed")
http.Error(w, "impersonation is not allowed", http.StatusForbidden)
return
}
ok, err := isAdmin(ctx, a.r)
if err != nil {
a.log.Error().Err(err).Msg("error checking if user is admin")
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
if !ok {
a.log.Error().Msg("user is not admin")
http.Error(w, "forbidden", http.StatusForbidden)
return
}
ctx, err = a.authenticateUser(cid, gwc)
if err != nil {
a.log.Error().Err(err).Msg("error authenticating user")
@@ -88,9 +123,7 @@ func (a *AuthAppService) HandleCreate(w http.ResponseWriter, r *http.Request) {
res, err := gwc.GenerateAppPassword(ctx, &applications.GenerateAppPasswordRequest{
TokenScope: scopes,
Label: "Generated via API",
Expiration: &types.Timestamp{
Seconds: uint64(time.Now().Add(time.Hour).Unix()),
},
Expiration: utils.TimeToTS(time.Now().Add(expiry)),
})
if err != nil {
a.log.Error().Err(err).Msg("error generating app password")
@@ -225,3 +258,34 @@ func buildClientID(userID, userName string) string {
return "username:" + userName
}
}
// isAdmin determines if the user in the context is an admin / has account management permissions
func isAdmin(ctx context.Context, rm *roles.Manager) (bool, error) {
logger := appctx.GetLogger(ctx)
u, ok := ctxpkg.ContextGetUser(ctx)
uid := u.GetId().GetOpaqueId()
if !ok || uid == "" {
logger.Error().Str("userid", uid).Msg("user not in context")
return false, errors.New("no user in context")
}
// get roles from context
roleIDs, ok := roles.ReadRoleIDsFromContext(ctx)
if !ok {
logger.Debug().Str("userid", uid).Msg("No roles in context, contacting settings service")
var err error
roleIDs, err = rm.FindRoleIDsForUser(ctx, uid)
if err != nil {
logger.Err(err).Str("userid", uid).Msg("failed to get roles for user")
return false, err
}
if len(roleIDs) == 0 {
logger.Err(err).Str("userid", uid).Msg("user has no roles")
return false, errors.New("user has no roles")
}
}
// check if permission is present in roles of the authenticated account
return rm.FindPermissionByID(ctx, roleIDs, settings.AccountManagementPermissionID) != nil, nil
}