package sql import ( "database/sql" "fmt" "net/url" "strconv" "github.com/lib/pq" sqlite3 "github.com/mattn/go-sqlite3" "github.com/sirupsen/logrus" "github.com/dexidp/dex/storage" ) const ( // postgres error codes pgErrUniqueViolation = "23505" // unique_violation ) // SQLite3 options for creating an SQL db. type SQLite3 struct { // File to File string `json:"file"` } // Open creates a new storage implementation backed by SQLite3 func (s *SQLite3) Open(logger logrus.FieldLogger) (storage.Storage, error) { conn, err := s.open(logger) if err != nil { return nil, err } return conn, nil } func (s *SQLite3) open(logger logrus.FieldLogger) (*conn, error) { db, err := sql.Open("sqlite3", s.File) if err != nil { return nil, err } if s.File == ":memory:" { // sqlite3 uses file locks to coordinate concurrent access. In memory // doesn't support this, so limit the number of connections to 1. db.SetMaxOpenConns(1) } errCheck := func(err error) bool { sqlErr, ok := err.(sqlite3.Error) if !ok { return false } return sqlErr.ExtendedCode == sqlite3.ErrConstraintPrimaryKey } c := &conn{db, flavorSQLite3, logger, errCheck} if _, err := c.migrate(); err != nil { return nil, fmt.Errorf("failed to perform migrations: %v", err) } return c, nil } const ( sslDisable = "disable" sslRequire = "require" sslVerifyCA = "verify-ca" sslVerifyFull = "verify-full" ) // PostgresSSL represents SSL options for Postgres databases. type PostgresSSL struct { Mode string CAFile string // Files for client auth. KeyFile string CertFile string } // Postgres options for creating an SQL db. type Postgres struct { Database string User string Password string Host string SSL PostgresSSL `json:"ssl" yaml:"ssl"` ConnectionTimeout int // Seconds } // Open creates a new storage implementation backed by Postgres. func (p *Postgres) Open(logger logrus.FieldLogger) (storage.Storage, error) { conn, err := p.open(logger) if err != nil { return nil, err } return conn, nil } func (p *Postgres) open(logger logrus.FieldLogger) (*conn, error) { v := url.Values{} set := func(key, val string) { if val != "" { v.Set(key, val) } } set("connect_timeout", strconv.Itoa(p.ConnectionTimeout)) set("sslkey", p.SSL.KeyFile) set("sslcert", p.SSL.CertFile) set("sslrootcert", p.SSL.CAFile) if p.SSL.Mode == "" { // Assume the strictest mode if unspecified. p.SSL.Mode = sslVerifyFull } set("sslmode", p.SSL.Mode) u := url.URL{ Scheme: "postgres", Host: p.Host, Path: "/" + p.Database, RawQuery: v.Encode(), } if p.User != "" { if p.Password != "" { u.User = url.UserPassword(p.User, p.Password) } else { u.User = url.User(p.User) } } db, err := sql.Open("postgres", u.String()) if err != nil { return nil, err } errCheck := func(err error) bool { sqlErr, ok := err.(*pq.Error) if !ok { return false } return sqlErr.Code == pgErrUniqueViolation } c := &conn{db, flavorPostgres, logger, errCheck} if _, err := c.migrate(); err != nil { return nil, fmt.Errorf("failed to perform migrations: %v", err) } return c, nil }