Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
olevitt committed Mar 22, 2024
1 parent 928ccc4 commit 20b8cbd
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 25 deletions.
8 changes: 5 additions & 3 deletions cmd/authentication-none.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import (

func NoAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("user", pkg.UserInfo{
requestContext := GetRequestContext(c)
requestContext.User = pkg.UserInfo{
Email: "[email protected]",
ID: "johndoe",
Name: "John Doe",
Groups: []string{},
IP: c.RemoteIP(),
Projects: []pkg.Project{{Name: "todo"}},
})
Projects: []pkg.Project{{Name: "todo", ID: "todooo"}},
}
SetRequestContext(c, requestContext)
c.Next()
}
}
11 changes: 4 additions & 7 deletions cmd/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ package cmd

import (
"context"
"fmt"
"net/http"
"strings"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin"
"github.com/inseefrlab/onyxia-api/internal/configuration"
pkg "github.com/inseefrlab/onyxia-api/pkg"
)

Expand All @@ -21,6 +19,7 @@ type Claims struct {

func AuthMiddleware(ctx context.Context, verifier *oidc.IDTokenVerifier) gin.HandlerFunc {
return func(c *gin.Context) {
requestContext := GetRequestContext(c)
tokenHeader := strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer ")
token, err := verifier.Verify(ctx, tokenHeader)
if err != nil {
Expand All @@ -35,17 +34,15 @@ func AuthMiddleware(ctx context.Context, verifier *oidc.IDTokenVerifier) gin.Han
var allClaims map[string]interface{}
token.Claims(&allClaims)
c.Set("claims", IDTokenClaims)
region, _ := c.Get("region")
fmt.Println(region.(configuration.Region).ID)
c.Set("user", pkg.UserInfo{
requestContext.User = pkg.UserInfo{
Email: IDTokenClaims.Email,
ID: IDTokenClaims.ID,
Name: IDTokenClaims.Name,
Groups: IDTokenClaims.Groups,
IP: c.RemoteIP(),
Projects: []pkg.Project{{Name: "todo"}},
})

}
SetRequestContext(c, requestContext)
c.Next()
}
}
42 changes: 36 additions & 6 deletions cmd/onboarding-handler.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,56 @@
package cmd

import (
"fmt"
"net/http"

"github.com/inseefrlab/onyxia-api/internal/helm"
"slices"

"github.com/gin-gonic/gin"
"github.com/inseefrlab/onyxia-api/internal/kubernetes"
)

type OnboardingRequest struct {
Group string `json:"group" `
}

// @Summary Init a namespace for a user or a group
// @Schemes
// @Description Create or replace the namespace of the user or the namespace of a group if the user is in the requested group and the according rbac policies. with the group prefix / user prefix of the region
// @Tags Onboarding
// @Consume json
// @Produce json
// @Success 200
// @Router /onboarding [post]
func onboarding(c *gin.Context) {
myServices := MyServices{}
for _, release := range helm.ListReleases() {
myServices.Apps = append(myServices.Apps, App{ID: release.Name, Chart: release.Chart.Name()})
var onboardingRequest OnboardingRequest
if err := c.BindJSON(&onboardingRequest); err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}

requestContext := GetRequestContext(c)
if requestContext.Region.Services.SingleNamespace {
c.AbortWithStatusJSON(400, gin.H{"error": "Instance is in single namespace mode"})
return
}

if !requestContext.Region.Services.AllowNamespaceCreation {
c.AbortWithStatusJSON(400, gin.H{"error": "This instance does not allow namespace creation"})
return
}

if onboardingRequest.Group != "" && !slices.Contains(requestContext.User.Groups, onboardingRequest.Group) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": fmt.Sprintf("User %s does not belong to group %s", requestContext.User.ID, onboardingRequest.Group)})
return
}

var namespace string
if onboardingRequest.Group == "" {
namespace = fmt.Sprintf("%s%s", requestContext.Region.Services.NamespacePrefix, requestContext.User.ID)
} else {
namespace = fmt.Sprintf("%s%s", requestContext.Region.Services.GroupNamespacePrefix, onboardingRequest.Group)
}
c.JSON(http.StatusOK, myServices)
kubernetes.InitNamespace(namespace)
}

func registerOnboardingHandlers(r *gin.RouterGroup) {
Expand Down
37 changes: 37 additions & 0 deletions cmd/project-resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cmd

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"
"github.com/inseefrlab/onyxia-api/pkg"
)

func ProjectResolver() gin.HandlerFunc {
return func(c *gin.Context) {
requestContext := GetRequestContext(c)
if len(requestContext.User.Projects) == 0 {
c.AbortWithStatusJSON(500, gin.H{"error": fmt.Sprintf("User %s has no project", requestContext.User.ID)})
return
}

headerProject := c.GetHeader("ONYXIA-PROJECT")
if headerProject == "" {
requestContext.Project = requestContext.User.Projects[0]
} else {
var foundProject pkg.Project
for _, project := range requestContext.User.Projects {
if project.ID == headerProject {
foundProject = project
requestContext.Project = project
}
}
if foundProject.ID == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Requested project not found"})
}
}
SetRequestContext(c, requestContext)
c.Next()
}
}
6 changes: 4 additions & 2 deletions cmd/region-resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,23 @@ func RegionResolver() gin.HandlerFunc {
}
defaultRegion := configuration.Config.Regions[0]
return func(c *gin.Context) {
requestContext := GetRequestContext(c)
headerRegion := c.GetHeader("ONYXIA-REGION")
if headerRegion == "" {
c.Set("region", defaultRegion)
requestContext.Region = defaultRegion
} else {
var foundRegion configuration.Region
for _, region := range configuration.Config.Regions {
if region.ID == headerRegion {
foundRegion = region
c.Set("region", foundRegion)
requestContext.Region = foundRegion
}
}
if foundRegion.ID == "" {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Requested region not found"})
}
}
SetRequestContext(c, requestContext)
c.Next()
}
}
28 changes: 28 additions & 0 deletions cmd/request-context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"github.com/gin-gonic/gin"
"github.com/inseefrlab/onyxia-api/internal/configuration"
"github.com/inseefrlab/onyxia-api/pkg"
)

type RequestContext struct {
User pkg.UserInfo
Project pkg.Project
Region configuration.Region
}

var requestContextKey = "requestContext"

func GetRequestContext(c *gin.Context) RequestContext {
context, exists := c.Get(requestContextKey)
if !exists {
context = RequestContext{}
c.Set(requestContextKey, context)
}
return context.(RequestContext)
}

func SetRequestContext(c *gin.Context, newContext RequestContext) {
c.Set(requestContextKey, newContext)
}
10 changes: 7 additions & 3 deletions cmd/user-handler.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package cmd

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"
pkg "github.com/inseefrlab/onyxia-api/pkg"
)

// @Summary Get user info
Expand All @@ -15,8 +15,12 @@ import (
// @Success 200
// @Router /user/info [get]
func userInfo(c *gin.Context) {
user, _ := c.Get("user")
c.JSON(http.StatusOK, user.(pkg.UserInfo))
requestContext := GetRequestContext(c)
fmt.Printf("Project %s", requestContext.Project.ID)
fmt.Println()
fmt.Printf("Region %s", requestContext.Region.ID)
fmt.Println()
c.JSON(http.StatusOK, requestContext.User)
}

func registerUserHandlers(r *gin.RouterGroup) {
Expand Down
6 changes: 4 additions & 2 deletions internal/configuration/configuration.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package configuration

import "strings"
import (
"strings"
)

type Configuration struct {
Authentication Authentication
Expand Down Expand Up @@ -43,7 +45,7 @@ type Region struct {
Services struct {
Type string `json:"type"`
SingleNamespace bool `json:"singleNamespace"`
AllowNamespaceCreation bool `json:"allowNamespaceCreation"`
AllowNamespaceCreation bool `json:"allowNamespaceCreation,omitempty"`
NamespaceLabels struct {
} `json:"namespaceLabels"`
NamespaceAnnotations struct {
Expand Down
11 changes: 11 additions & 0 deletions internal/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"

"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -51,3 +52,13 @@ func GetEvents(namespace string) watch.Interface {
events, _ := clientset.EventsV1().Events(namespace).Watch(context.TODO(), metav1.ListOptions{})
return events
}

func InitNamespace(namespace string) {
namespaceToCreate := corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
Labels: map[string]string{"onyxia_owner": "todo"},
},
}
clientset.CoreV1().Namespaces().Create(context.TODO(), &namespaceToCreate, metav1.CreateOptions{})
}
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var defaultConfiguration string

func main() {
configuration.LoadConfiguration(defaultConfiguration)
zap.ReplaceGlobals(zap.Must(zap.NewProduction()))
r := gin.Default()
baseRoutes := r.Group(configuration.Config.RootPath)
docs.SwaggerInfo.Description = "Swagger"
Expand All @@ -37,7 +38,6 @@ func main() {
privateRoutes := baseRoutes.Group("/")
publicRoutes := baseRoutes.Group("/public")
privateRoutes.Use(cmd.RegionResolver())
zap.ReplaceGlobals(zap.Must(zap.NewProduction()))
if strings.EqualFold(configuration.Config.Authentication.Mode, "openidconnect") {
fmt.Printf("Using OIDC authentication with issuer %s", configuration.Config.OIDC.IssuerURI)
fmt.Println()
Expand All @@ -58,7 +58,7 @@ func main() {
} else {
privateRoutes.Use(cmd.NoAuthMiddleware())
}

privateRoutes.Use(cmd.ProjectResolver())
kubernetes.InitClient()

cmd.RegisterPrivateHandlers(privateRoutes)
Expand Down

0 comments on commit 20b8cbd

Please sign in to comment.