Compare commits

..

4 commits

Author SHA1 Message Date
fa20b9583b chore: refactor serve command
All checks were successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful
2023-10-29 13:32:09 +01:00
5753630553 feat #43: add cli command to manage backends 2023-10-29 13:31:52 +01:00
930e94f820 feat #43: add service to handle backends in the storage 2023-10-29 13:29:48 +01:00
897b9273fc Add envrc to facilitate development 2023-10-29 13:27:13 +01:00
18 changed files with 529 additions and 240 deletions

18
.envrc Normal file
View file

@ -0,0 +1,18 @@
# Can be debug,info,warning,error
export LOG_LEVEL=debug
# Can be net,unix
export SERVER_MODE=net
export SERVER_HOST="0.0.0.0"
export SERVER_PORT="5000"
# SERVER_SOCK_PATH = ""
export STORAGE_TYPE="sqlite"
export STORAGE_FILEPATH="./build/polyculeconnect.db"
# STORAGE_HOST = "127.0.0.1"
# STORAGE_PORT = "5432"
# STORAGE_DB = "polyculeconnect"
# STORAGE_USER = "polyculeconnect"
# STORAGE_PASSWORD = "polyculeconnect"
# STORAGE_SSL_MODE = "disable"
# STORAGE_SSL_CA_FILE = ""

View file

@ -1,11 +1,11 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend"
"github.com/spf13/cobra"
)
@ -15,7 +15,6 @@ var (
backendIssuer string
)
// backendAddCmd represents the add command
var backendAddCmd = &cobra.Command{
Use: "add",
Short: "Add a new backend to the storage",
@ -28,22 +27,39 @@ Parameters to provide:
authentication
- issuer: Full hostname of the OIDC provider, e.g. 'https://github.com'`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("add called")
addNewBackend()
},
}
func addNewBackend() {
c := utils.InitConfig("")
s := utils.InitStorage(c)
clientID, clientSecret, err := services.GenerateClientIDSecret()
if err != nil {
utils.Failf("Failed to generate client id or secret: %s", err.Error())
}
backendConf := backend.BackendConfig{
Issuer: backendIssuer,
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURI: c.RedirectURI(),
ID: backendID,
Name: backendName,
}
if err := backend.New(s).AddBackend(backendConf); err != nil {
utils.Failf("Failed to add new backend to storage: %s", err.Error())
}
fmt.Printf("New backend %s added.\n", backendName)
printProperty("Client ID", clientID)
printProperty("Client secret", clientSecret)
}
func init() {
backendCmd.AddCommand(backendAddCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// backendAddCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// backendAddCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
backendAddCmd.Flags().StringVarP(&backendID, "id", "i", "", "ID to identify the backend in the storage")
backendAddCmd.Flags().StringVarP(&backendName, "name", "n", "", "Name to represent the backend")
backendAddCmd.Flags().StringVarP(&backendIssuer, "issuer", "d", "", "Full hostname of the backend")

View file

@ -1,16 +1,12 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd"
"github.com/spf13/cobra"
)
// backendCmd represents the backend command
var backendCmd = &cobra.Command{
Use: "backend",
Short: "A brief description of your command",
@ -25,16 +21,10 @@ to quickly create a Cobra application.`,
},
}
func init() {
rootCmd.AddCommand(backendCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// backendCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// backendCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
func printProperty(key, value string) {
fmt.Printf("\t- %s: %s\n", key, value)
}
func init() {
cmd.RootCmd.AddCommand(backendCmd)
}

View file

@ -0,0 +1,38 @@
package cmd
import (
"errors"
"fmt"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend"
"github.com/dexidp/dex/storage"
"github.com/spf13/cobra"
)
var backendRemoveCmd = &cobra.Command{
Use: "remove <backend_id>",
Short: "Remove a backend",
Long: `Remove the backend with the given ID from the database.
If the backend is not found in the database, no error is returned`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
removeBackend(args[0])
},
}
func removeBackend(backendID string) {
s := utils.InitStorage(utils.InitConfig(""))
if err := backend.New(s).RemoveBackend(backendID); err != nil {
if !errors.Is(err, storage.ErrNotFound) {
utils.Failf("Failed to remove backend: %s", err.Error())
}
}
fmt.Println("Backend deleted")
}
func init() {
backendCmd.AddCommand(backendRemoveCmd)
}

View file

@ -0,0 +1,67 @@
package cmd
import (
"errors"
"fmt"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend"
"github.com/dexidp/dex/storage"
"github.com/spf13/cobra"
)
var backendShowCmd = &cobra.Command{
Use: "show [backend_id]",
Short: "Display installed backends",
Long: `Display the configuration for the backends.
Pass the commands without arguments to display the list of currently installed backends
Pass the optional 'id' argument to display the configuration for this specific backend`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
s := utils.InitStorage(utils.InitConfig(""))
if len(args) > 0 {
showBackend(args[0], backend.New(s))
} else {
listBackends(backend.New(s))
}
},
}
func showBackend(backendId string, backendService backend.Service) {
backendConfig, err := backendService.GetBackend(backendId)
if err != nil {
if errors.Is(err, storage.ErrNotFound) {
utils.Failf("Backend with ID %s does not exist\n", backendId)
}
utils.Failf("Failed to get config for backend %s: %q\n", backendId, err.Error())
}
fmt.Println("Backend config:")
printProperty("ID", backendConfig.ID)
printProperty("Name", backendConfig.Name)
printProperty("Issuer", backendConfig.Issuer)
printProperty("Client ID", backendConfig.ClientID)
printProperty("Client secret", backendConfig.ClientSecret)
printProperty("Redirect URI", backendConfig.RedirectURI)
}
func listBackends(backendService backend.Service) {
backends, err := backendService.ListBackends()
if err != nil {
utils.Failf("Failed to list backends: %q\n", err.Error())
}
if len(backends) == 0 {
fmt.Println("No backend configured")
return
}
for _, b := range backends {
fmt.Printf("\t - %s: (%s) - %s\n", b.ID, b.Name, b.Issuer)
}
}
func init() {
backendCmd.AddCommand(backendShowCmd)
}

View file

@ -1,39 +0,0 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// backendRemoveCmd represents the remove command
var backendRemoveCmd = &cobra.Command{
Use: "remove",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("remove called")
},
}
func init() {
backendCmd.AddCommand(backendRemoveCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// backendRemoveCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// backendRemoveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View file

@ -1,97 +0,0 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"errors"
"fmt"
"os"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/config"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend"
"github.com/dexidp/dex/storage"
"github.com/spf13/cobra"
)
// backendShowCmd represents the show command for backends
var backendShowCmd = &cobra.Command{
Use: "show [backend_id]",
Short: "Display installed backends",
Long: `Display the configuration for the backends.
Pass the commands without arguments to display the list of currently installed backends
Pass the optional 'id' argument to display the configuration for this specific backend`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
conf, err := config.New(configPath)
if err != nil {
panic(err)
}
s, err := services.InitStorage(conf)
if err != nil {
panic(err)
}
if len(args) > 0 {
showBackend(args[0], backend.New(s))
} else {
listBackends(backend.New(s))
}
},
}
func printProperty(key, value string) {
fmt.Printf("\t- %s: %s\n", key, value)
}
func showBackend(backendId string, backendService backend.Service) {
backendConfig, err := backendService.GetBackend(backendId)
if err != nil {
if errors.Is(err, storage.ErrNotFound) {
fmt.Fprintf(os.Stderr, "Backend with ID %s does not exist\n", backendId)
} else {
fmt.Fprintf(os.Stderr, "Failed to get config for backend %s: %q\n", backendId, err.Error())
}
return
}
fmt.Println("Backend config:")
printProperty("ID", backendConfig.Storage.ID)
printProperty("Name", backendConfig.Storage.Name)
printProperty("Issuer", backendConfig.OIDC.Issuer)
printProperty("Client ID", backendConfig.OIDC.ClientID)
printProperty("Client secret", backendConfig.OIDC.ClientSecret)
}
func listBackends(backendService backend.Service) {
backends, err := backendService.ListBackends()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to list backends: %q\n", err.Error())
return
}
if len(backends) == 0 {
fmt.Println("No backend configured")
} else {
for _, b := range backends {
fmt.Printf("\t - backend %s: id %s issuer %s\n", b.Storage.Name, b.Storage.ID, b.OIDC.Issuer)
}
}
}
func init() {
backendCmd.AddCommand(backendShowCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// backendShowCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// backendShowCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View file

@ -6,8 +6,8 @@ import (
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "polyculeconnect",
Short: "You're in their DM, I'm in their SSO",
Long: `PolyculeConnect is a SSO OpenIDConnect provider allowing multiple authentication
@ -20,7 +20,7 @@ backends, and enabling authentication federation among several infrastructures.`
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
err := RootCmd.Execute()
if err != nil {
os.Exit(1)
}
@ -38,5 +38,5 @@ func init() {
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
// Disable the default `completion` command to generate the autocompletion files
rootCmd.Root().CompletionOptions.DisableDefaultCmd = true
RootCmd.Root().CompletionOptions.DisableDefaultCmd = true
}

View file

@ -1,4 +1,4 @@
package cmd
package serve
import (
"context"
@ -6,12 +6,12 @@ import (
"os/signal"
"time"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/config"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/utils"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/connector"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/server"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services"
"github.com/dexidp/dex/connector/oidc"
dex_server "github.com/dexidp/dex/server"
"github.com/prometheus/client_golang/prometheus"
"github.com/spf13/cobra"
@ -35,18 +35,11 @@ variables`,
func serve() {
mainCtx, cancel := context.WithCancel(context.Background())
conf, err := config.New(configPath)
if err != nil {
panic(err)
}
conf := utils.InitConfig(configPath)
logger.Init(conf.LogLevel)
logger.L.Infof("Initialized logger with level %v", conf.LogLevel)
storageType, err := services.InitStorage(conf)
if err != nil {
logger.L.Fatalf("Failed to initialize storage backend: %s", err.Error())
}
storageType := utils.InitStorage(conf)
logger.L.Infof("Initialized storage backend %q", conf.StorageType)
dexConf := dex_server.Config{
Web: dex_server.WebConfig{
@ -74,14 +67,7 @@ func serve() {
connectorIDs = append(connectorIDs, conn.ID)
}
backend := config.BackendConfig{
Config: &oidc.Config{},
Name: "RefuseAll",
ID: "null",
Type: "refuseAll",
}
if err := services.CreateConnector(&backend, &dexConf, connectorIDs); err != nil {
if err := services.AddDefaultBackend(storageType); err != nil {
logger.L.Errorf("Failed to add connector for backend RefuseAll to stage: %s", err.Error())
}
@ -140,7 +126,7 @@ func serve() {
}
func init() {
rootCmd.AddCommand(serveCmd)
cmd.RootCmd.AddCommand(serveCmd)
// Here you will define your flags and configuration settings.

View file

@ -0,0 +1,40 @@
package utils
import (
"fmt"
"os"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/config"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services"
"github.com/dexidp/dex/storage"
)
// Fail displays the given error to stderr and exits the program with a returncode 1
func Fail(errMsg string) {
fmt.Fprintln(os.Stderr, errMsg)
os.Exit(1)
}
// Fail displays the given formatted error to stderr and exits the program with a returncode 1
func Failf(msg string, args ...any) {
fmt.Fprintf(os.Stderr, msg+"\n", args...)
os.Exit(1)
}
// InitConfig inits the configuration, and fails the program if an error occurs
func InitConfig(configPath string) *config.AppConfig {
conf, err := config.New(configPath)
if err != nil {
Failf("Failed to load the configuration: %s", err.Error())
}
return conf
}
// Initstorage inits the storage, and fails the program if an error occurs
func InitStorage(config *config.AppConfig) storage.Storage {
s, err := services.InitStorage(config)
if err != nil {
Failf("Failed to init the storage: %s", err.Error())
}
return s
}

View file

@ -66,6 +66,7 @@ const (
defaultStorageSSLCaFile = ""
)
// Deprecated: remove when we finally drop the JSON config
type BackendConfig struct {
Config *oidc.Config `json:"config"`
Name string `json:"name"`
@ -145,6 +146,10 @@ func (ac *AppConfig) getConfFromEnv() {
ac.StorageConfig.Ssl.Mode = getStringFromEnv(varStorageSSLMode, defaultStorageSSLMode)
}
func (ac *AppConfig) RedirectURI() string {
return ac.OpenConnectConfig.Issuer + "/callback"
}
func New(filepath string) (*AppConfig, error) {
var conf AppConfig
conf.StorageConfig = &StorageConfig{}

View file

@ -6,10 +6,18 @@ import (
"github.com/dexidp/dex/connector"
"github.com/dexidp/dex/pkg/log"
"github.com/dexidp/dex/storage"
)
const TypeRefuseAll = "refuseAll"
var RefuseAllConnectorConfig storage.Connector = storage.Connector{
ID: "null",
Name: "RefuseAll",
Type: TypeRefuseAll,
Config: nil,
}
type RefuseAllConfig struct{}
func (c *RefuseAllConfig) Open(id string, logger log.Logger) (connector.Connector, error) {

View file

@ -1,6 +1,10 @@
package main
import "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd"
import (
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd"
_ "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/backend"
_ "git.faercol.me/faercol/polyculeconnect/polyculeconnect/cmd/serve"
)
func main() {
cmd.Execute()

View file

@ -13,18 +13,51 @@ import (
var ErrUnsupportedType = errors.New("unsupported connector type")
type BackendConfig struct {
OIDC oidc.Config
Storage storage.Connector
ID string
Name string
Issuer string
ClientID string
ClientSecret string
RedirectURI string
}
func (bc *BackendConfig) OIDC() oidc.Config {
return oidc.Config{
Issuer: bc.Issuer,
ClientID: bc.ClientID,
ClientSecret: bc.ClientSecret,
RedirectURI: bc.RedirectURI,
}
}
func (bc *BackendConfig) Storage() (storage.Connector, error) {
oidcJSON, err := json.Marshal(bc.OIDC())
if err != nil {
return storage.Connector{}, fmt.Errorf("failed to serialize oidc config: %w", err)
}
return storage.Connector{
ID: bc.ID,
Type: "oidc",
Name: bc.Name,
Config: oidcJSON,
}, nil
}
func (bc *BackendConfig) FromConnector(connector storage.Connector) error {
var oidc oidc.Config
if connector.Type != "oidc" {
return ErrUnsupportedType
}
if err := json.Unmarshal(connector.Config, &bc.OIDC); err != nil {
if err := json.Unmarshal(connector.Config, &oidc); err != nil {
return fmt.Errorf("invalid OIDC config: %w", err)
}
bc.Storage = connector
bc.ID = connector.ID
bc.Name = connector.Name
bc.ClientID = oidc.ClientID
bc.ClientSecret = oidc.ClientSecret
bc.Issuer = oidc.Issuer
bc.RedirectURI = oidc.RedirectURI
return nil
}
@ -74,7 +107,11 @@ func (cbs *concreteBackendService) GetBackend(connectorID string) (BackendConfig
}
func (cbs *concreteBackendService) AddBackend(config BackendConfig) error {
return cbs.s.CreateConnector(config.Storage)
storageConf, err := config.Storage()
if err != nil {
return fmt.Errorf("failed to create storage configuration: %w", err)
}
return cbs.s.CreateConnector(storageConf)
}
func (cbs *concreteBackendService) RemoveBackend(connectorID string) error {

View file

@ -0,0 +1,215 @@
package backend_test
import (
"encoding/json"
"fmt"
"testing"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/connector"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend"
"github.com/dexidp/dex/storage"
"github.com/dexidp/dex/storage/memory"
logt "github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
testDomain string = "https://test.domain.com"
testClientID string = "this_is_an_id"
testClientSecret string = "this_is_a_secret"
testRedirectURI string = "http://127.0.0.1:5000/callback"
testConnectorName string = "Test connector"
)
func generateConnector(id string) storage.Connector {
confJson := fmt.Sprintf(`{
"issuer": "%s",
"clientID": "%s",
"clientSecret": "%s",
"redirectURI": "%s"
}`, testDomain, testClientID, testClientSecret, testRedirectURI)
storageConfig := storage.Connector{
ID: id,
Name: testConnectorName,
Type: "oidc",
Config: []byte(confJson),
}
return storageConfig
}
func generateConfig(id string) backend.BackendConfig {
return backend.BackendConfig{
ID: id,
Name: testConnectorName,
Issuer: testDomain,
ClientID: testClientID,
ClientSecret: testClientSecret,
RedirectURI: testRedirectURI,
}
}
func checkStrInMap(t *testing.T, vals map[string]interface{}, key, expected string) {
rawVal, ok := vals[key]
require.Truef(t, ok, "missing key %s", key)
strVal, ok := rawVal.(string)
require.Truef(t, ok, "invalid string format %v", rawVal)
assert.Equal(t, expected, strVal, "unexpected value")
}
func initStorage(t *testing.T) storage.Storage {
logger, _ := logt.NewNullLogger()
s := memory.New(logger)
require.NoError(t, s.CreateConnector(connector.RefuseAllConnectorConfig))
require.NoError(t, s.CreateConnector(generateConnector("test0")))
require.NoError(t, s.CreateConnector(generateConnector("test1")))
return s
}
func TestBackendConfigFromConnector(t *testing.T) {
connector := generateConnector("test")
var bc backend.BackendConfig
require.NoError(t, bc.FromConnector(connector))
assert.Equal(t, testDomain, bc.Issuer)
assert.Equal(t, testClientID, bc.ClientID)
assert.Equal(t, testClientSecret, bc.ClientSecret)
assert.Equal(t, testRedirectURI, bc.RedirectURI)
assert.Equal(t, testConnectorName, bc.Name)
assert.Equal(t, "test", bc.ID)
}
func TestBackendConfigInvalidType(t *testing.T) {
connector := generateConnector("test")
connector.Type = "test"
var bc backend.BackendConfig
assert.ErrorIs(t, bc.FromConnector(connector), backend.ErrUnsupportedType)
}
func TestBackendConfigInvalidOIDCConfig(t *testing.T) {
connector := generateConnector("test")
connector.Config = []byte("toto")
var bc backend.BackendConfig
assert.ErrorContains(t, bc.FromConnector(connector), "invalid OIDC config")
}
func TestOIDCConfigFromBackendConfig(t *testing.T) {
conf := generateConfig("test")
oidcConf := conf.OIDC()
assert.Equal(t, testDomain, oidcConf.Issuer)
assert.Equal(t, testClientID, oidcConf.ClientID)
assert.Equal(t, testClientSecret, oidcConf.ClientSecret)
assert.Equal(t, testRedirectURI, oidcConf.RedirectURI)
}
func TestConnectorConfigFromBackendConfig(t *testing.T) {
conf := generateConfig("test")
con, err := conf.Storage()
require.NoError(t, err)
// The OIDC config is stored as JSON data, we just want the raw keys here
var oidcConf map[string]interface{}
require.NoError(t, json.Unmarshal(con.Config, &oidcConf))
assert.Equal(t, "oidc", con.Type)
assert.Equal(t, "test", con.ID)
assert.Equal(t, testConnectorName, con.Name)
checkStrInMap(t, oidcConf, "issuer", testDomain)
checkStrInMap(t, oidcConf, "clientID", testClientID)
checkStrInMap(t, oidcConf, "clientSecret", testClientSecret)
checkStrInMap(t, oidcConf, "redirectURI", testRedirectURI)
}
func TestListBackendsEmpty(t *testing.T) {
logger, _ := logt.NewNullLogger()
s := memory.New(logger)
// add the default refuse all connector, it should not be visible in the list
require.NoError(t, s.CreateConnector(connector.RefuseAllConnectorConfig))
srv := backend.New(s)
backends, err := srv.ListBackends() // empty list, and no error
require.NoError(t, err)
require.Len(t, backends, 0)
}
func TestListBackendsNotEmpty(t *testing.T) {
s := initStorage(t)
srv := backend.New(s)
backends, err := srv.ListBackends() // empty list, and no error
expectedIds := []string{"test0", "test1"}
require.NoError(t, err)
assert.Len(t, backends, 2)
for _, c := range backends {
assert.Contains(t, expectedIds, c.ID)
}
}
func TestGetBackend(t *testing.T) {
s := initStorage(t)
srv := backend.New(s)
t.Run("OK", func(t *testing.T) {
conf, err := srv.GetBackend("test0")
require.NoError(t, err)
assert.Equal(t, testDomain, conf.Issuer)
assert.Equal(t, testClientID, conf.ClientID)
assert.Equal(t, testClientSecret, conf.ClientSecret)
assert.Equal(t, testRedirectURI, conf.RedirectURI)
assert.Equal(t, testConnectorName, conf.Name)
assert.Equal(t, "test0", conf.ID)
})
t.Run("Not exist", func(t *testing.T) {
_, err := srv.GetBackend("toto")
assert.ErrorIs(t, err, storage.ErrNotFound)
})
t.Run("Invalid type", func(t *testing.T) {
_, err := srv.GetBackend("null") // null has a RefuseAll type, which is unsupported here
assert.ErrorIs(t, err, backend.ErrUnsupportedType)
})
}
func TestAddBackend(t *testing.T) {
s := initStorage(t)
srv := backend.New(s)
t.Run("OK", func(t *testing.T) {
conf := generateConfig("test_add")
require.NoError(t, srv.AddBackend(conf))
var parsedConf backend.BackendConfig
storageConf, err := s.GetConnector("test_add")
require.NoError(t, err)
require.NoError(t, parsedConf.FromConnector(storageConf))
assert.Equal(t, conf, parsedConf)
})
t.Run("Already exists", func(t *testing.T) {
require.ErrorIs(t, srv.AddBackend(generateConfig("test0")), storage.ErrAlreadyExists)
})
}
func TestRemoveBackend(t *testing.T) {
s := initStorage(t)
srv := backend.New(s)
t.Run("OK", func(t *testing.T) {
require.NoError(t, srv.AddBackend(generateConfig("to_remove")))
_, err := s.GetConnector("to_remove")
require.NoError(t, err) // no error means it's present
require.NoError(t, srv.RemoveBackend("to_remove"))
_, err = s.GetConnector("to_remove")
assert.ErrorIs(t, err, storage.ErrNotFound) // means it's been deleted
})
t.Run("No present", func(t *testing.T) {
require.ErrorIs(t, srv.RemoveBackend("toto"), storage.ErrNotFound)
})
}

View file

@ -1,42 +0,0 @@
package test
import "github.com/dexidp/dex/storage"
type MockBackendService struct {
Connectors map[string]storage.Connector
ListError error
GetError error
AddError error
RemoveError error
}
func (mbs *MockBackendService) ListConnectors() ([]storage.Connector, error) {
var res []storage.Connector
for _, c := range mbs.Connectors {
res = append(res, c)
}
return res, mbs.ListError
}
func (mbs *MockBackendService) GetConnector(connectorID string) (storage.Connector, error) {
if res, ok := mbs.Connectors[connectorID]; ok {
return res, mbs.GetError
}
return storage.Connector{}, storage.ErrNotFound
}
func (mbs *MockBackendService) AddConnector(config storage.Connector) error {
if _, ok := mbs.Connectors[config.ID]; ok {
return storage.ErrAlreadyExists
}
if mbs.AddError != nil {
return mbs.AddError
}
mbs.Connectors[config.ID] = config
return nil
}
func (mbs *MockBackendService) RemoveConnector(connectorID string) error {
delete(mbs.Connectors, connectorID)
return mbs.RemoveError
}

View file

@ -0,0 +1,34 @@
package services
import (
"crypto/rand"
"encoding/hex"
"fmt"
)
// size in bytes of the client ids and secrets
const idSecretSize = 32
func generateRandomHex(size int) (string, error) {
raw := make([]byte, size)
n, err := rand.Read(raw)
if err != nil {
return "", fmt.Errorf("failed to read from random generator: %w", err)
}
if n != size {
return "", fmt.Errorf("failed to read from random generator (%d/%d)", n, size)
}
return hex.EncodeToString(raw), nil
}
func GenerateClientIDSecret() (string, string, error) {
clientID, err := generateRandomHex(idSecretSize)
if err != nil {
return "", "", fmt.Errorf("failed to generate client id: %w", err)
}
clientSecret, err := generateRandomHex(idSecretSize)
if err != nil {
return "", "", fmt.Errorf("failed to generate client secret: %w", err)
}
return clientID, clientSecret, nil
}

View file

@ -1,9 +1,11 @@
package services
import (
"errors"
"fmt"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/config"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/connector"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/logger"
"github.com/dexidp/dex/storage"
"github.com/dexidp/dex/storage/memory"
@ -29,3 +31,10 @@ func InitStorage(conf *config.AppConfig) (storage.Storage, error) {
}
return storageType, nil
}
func AddDefaultBackend(s storage.Storage) error {
if err := s.CreateConnector(connector.RefuseAllConnectorConfig); err != nil && !errors.Is(err, storage.ErrAlreadyExists) {
return fmt.Errorf("failed to add default backend: %w", err)
}
return nil
}