Răsfoiți Sursa

saml basic DB and types with validate endpoint

Mohammed Nafees 3 ani în urmă
părinte
comite
816ce81954

+ 61 - 0
api/server/handlers/saml/validate.go

@@ -0,0 +1,61 @@
+package saml
+
+import (
+	"errors"
+	"fmt"
+	"net/http"
+	"net/mail"
+	"strings"
+
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"gorm.io/gorm"
+)
+
+type ValidateSAMLHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewValidateSAMLHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *ValidateSAMLHandler {
+	return &ValidateSAMLHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (h *ValidateSAMLHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	request := &types.ValidateSAMLRequest{}
+
+	if ok := h.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	addr, err := mail.ParseAddress(request.Email)
+
+	if err != nil {
+		h.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("invalid email address"), http.StatusBadRequest))
+		return
+	}
+
+	domain := addr.Address[strings.Index(addr.Address, "@")+1:]
+
+	integ, err := h.Repo().SAMLIntegration().ValidateSAMLIntegration(domain)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			h.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no SAML integration found for this email")))
+			return
+		}
+
+		h.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
+
+	http.Redirect(w, r, integ.SignOnURL, http.StatusFound)
+}

+ 51 - 0
api/server/router/base.go

@@ -9,6 +9,7 @@ import (
 	"github.com/porter-dev/porter/api/server/handlers/healthcheck"
 	"github.com/porter-dev/porter/api/server/handlers/metadata"
 	"github.com/porter-dev/porter/api/server/handlers/release"
+	"github.com/porter-dev/porter/api/server/handlers/saml"
 	"github.com/porter-dev/porter/api/server/handlers/user"
 	"github.com/porter-dev/porter/api/server/handlers/webhook"
 	"github.com/porter-dev/porter/api/server/shared"
@@ -542,5 +543,55 @@ func GetBaseRoutes(
 		})
 	}
 
+	// POST /api/login/saml/validate -> saml.NewValidateSAMLHandler
+	samlValidateEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbCreate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: "/login/saml/validate",
+			},
+			Scopes: []types.PermissionScope{},
+		},
+	)
+
+	samlValidateHandler := saml.NewValidateSAMLHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: samlValidateEndpoint,
+		Handler:  samlValidateHandler,
+		Router:   r,
+	})
+
+	// // GET /api/login/saml/{idp} -> saml.NewSAMLLoginHandler
+	// samlLoginEndpoint := factory.NewAPIEndpoint(
+	// 	&types.APIRequestMetadata{
+	// 		Verb:   types.APIVerbGet,
+	// 		Method: types.HTTPVerbGet,
+	// 		Path: &types.Path{
+	// 			Parent:       basePath,
+	// 			RelativePath: "/login/saml/{idp}",
+	// 		},
+	// 		Scopes: []types.PermissionScope{},
+	// 	},
+	// )
+
+	// samlLoginHandler := credentials.NewGetCredentialsHandler(
+	// 	config,
+	// 	factory.GetDecoderValidator(),
+	// 	factory.GetResultWriter(),
+	// )
+
+	// routes = append(routes, &router.Route{
+	// 	Endpoint: samlLoginEndpoint,
+	// 	Handler:  samlLoginHandler,
+	// 	Router:   r,
+	// })
+
 	return routes
 }

+ 11 - 0
api/types/saml.go

@@ -0,0 +1,11 @@
+package types
+
+type IDPType string
+
+const (
+	IDPTypeOkta IDPType = "okta"
+)
+
+type ValidateSAMLRequest struct {
+	Email string `json:"email"`
+}

+ 15 - 0
internal/models/saml/saml_integration.go

@@ -0,0 +1,15 @@
+package saml
+
+import (
+	"github.com/porter-dev/porter/api/types"
+	"gorm.io/gorm"
+)
+
+type SAMLIntegration struct {
+	gorm.Model
+
+	Domains       string
+	IntegrationID uint
+	Type          types.IDPType
+	SignOnURL     string
+}

+ 6 - 0
internal/repository/gorm/repository.go

@@ -49,6 +49,7 @@ type GormRepository struct {
 	tag                       repository.TagRepository
 	stack                     repository.StackRepository
 	monitor                   repository.MonitorTestResultRepository
+	samlIntegration           repository.SAMLIntegrationRepository
 }
 
 func (t *GormRepository) User() repository.UserRepository {
@@ -219,6 +220,10 @@ func (t *GormRepository) MonitorTestResult() repository.MonitorTestResultReposit
 	return t.monitor
 }
 
+func (t *GormRepository) SAMLIntegration() repository.SAMLIntegrationRepository {
+	return t.samlIntegration
+}
+
 // NewRepository returns a Repository which persists users in memory
 // and accepts a parameter that can trigger read/write errors
 func NewRepository(db *gorm.DB, key *[32]byte, storageBackend credentials.CredentialStorage) repository.Repository {
@@ -265,5 +270,6 @@ func NewRepository(db *gorm.DB, key *[32]byte, storageBackend credentials.Creden
 		tag:                       NewTagRepository(db),
 		stack:                     NewStackRepository(db),
 		monitor:                   NewMonitorTestResultRepository(db),
+		samlIntegration:           NewSAMLIntegrationRepository(db),
 	}
 }

+ 30 - 0
internal/repository/gorm/saml.go

@@ -0,0 +1,30 @@
+package gorm
+
+import (
+	"github.com/porter-dev/porter/internal/models/saml"
+	"github.com/porter-dev/porter/internal/repository"
+	"gorm.io/gorm"
+)
+
+// SAMLIntegrationRepository uses gorm.DB for querying the database
+type SAMLIntegrationRepository struct {
+	db *gorm.DB
+}
+
+// NewSAMLIntegrationRepository returns a SAMLIntegrationRepository which uses
+// gorm.DB for querying the database
+func NewSAMLIntegrationRepository(db *gorm.DB) repository.SAMLIntegrationRepository {
+	return &SAMLIntegrationRepository{db}
+}
+
+func (repo *SAMLIntegrationRepository) ValidateSAMLIntegration(domain string) (*saml.SAMLIntegration, error) {
+	integ := &saml.SAMLIntegration{}
+
+	if err := repo.db.Raw(
+		"SELECT * FROM saml_integrations WHERE ? = ANY (string_to_array(saml_integrations.domains, ','))", domain).
+		First(integ).Error; err != nil {
+		return nil, err
+	}
+
+	return integ, nil
+}

+ 1 - 0
internal/repository/repository.go

@@ -43,4 +43,5 @@ type Repository interface {
 	Tag() TagRepository
 	Stack() StackRepository
 	MonitorTestResult() MonitorTestResultRepository
+	SAMLIntegration() SAMLIntegrationRepository
 }

+ 9 - 0
internal/repository/saml.go

@@ -0,0 +1,9 @@
+package repository
+
+import (
+	"github.com/porter-dev/porter/internal/models/saml"
+)
+
+type SAMLIntegrationRepository interface {
+	ValidateSAMLIntegration(domain string) (*saml.SAMLIntegration, error)
+}

+ 6 - 0
internal/repository/test/repository.go

@@ -47,6 +47,7 @@ type TestRepository struct {
 	tag                       repository.TagRepository
 	stack                     repository.StackRepository
 	monitor                   repository.MonitorTestResultRepository
+	samlIntegration           repository.SAMLIntegrationRepository
 }
 
 func (t *TestRepository) User() repository.UserRepository {
@@ -217,6 +218,10 @@ func (t *TestRepository) MonitorTestResult() repository.MonitorTestResultReposit
 	return t.monitor
 }
 
+func (t *TestRepository) SAMLIntegration() repository.SAMLIntegrationRepository {
+	return t.samlIntegration
+}
+
 // NewRepository returns a Repository which persists users in memory
 // and accepts a parameter that can trigger read/write errors
 func NewRepository(canQuery bool, failingMethods ...string) repository.Repository {
@@ -263,5 +268,6 @@ func NewRepository(canQuery bool, failingMethods ...string) repository.Repositor
 		tag:                       NewTagRepository(),
 		stack:                     NewStackRepository(),
 		monitor:                   NewMonitorTestResultRepository(canQuery),
+		samlIntegration:           NewSAMLIntegrationRepository(canQuery),
 	}
 }

+ 28 - 0
internal/repository/test/saml.go

@@ -0,0 +1,28 @@
+package test
+
+import (
+	"github.com/porter-dev/porter/internal/models/saml"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// SAMLIntegrationRepository implements repository.SAMLIntegrationRepository
+type SAMLIntegrationRepository struct {
+	canQuery     bool
+	integrations []*saml.SAMLIntegration
+}
+
+// NewSAMLIntegrationRepository will return errors if canQuery is false
+func NewSAMLIntegrationRepository(canQuery bool) repository.SAMLIntegrationRepository {
+	return &SAMLIntegrationRepository{
+		canQuery,
+		[]*saml.SAMLIntegration{},
+	}
+}
+
+func (repo *SAMLIntegrationRepository) ValidateSAMLIntegration(domain string) (*saml.SAMLIntegration, error) {
+	integ := &saml.SAMLIntegration{}
+
+	// TODO: fix test query
+
+	return integ, nil
+}