Mercurial > gemma
view pkg/auth/opendb.go @ 5655:ace5358e1527
Fix “Available fairway depth” button in BN popup.
author | Sascha Wilde <wilde@sha-bang.de> |
---|---|
date | Thu, 14 Sep 2023 18:52:18 +0200 |
parents | 1e6053a4ed98 |
children |
line wrap: on
line source
// This is Free Software under GNU Affero General Public License v >= 3.0 // without warranty, see README.md and license for details. // // SPDX-License-Identifier: AGPL-3.0-or-later // License-Filename: LICENSES/AGPL-3.0.txt // // Copyright (C) 2018 by via donau // – Österreichische Wasserstraßen-Gesellschaft mbH // Software engineering by Intevation GmbH // // Author(s): // * Sascha L. Teichmann <sascha.teichmann@intevation.de> package auth import ( "context" "database/sql" "errors" "net/http" "sync" "github.com/jackc/pgx" "github.com/jackc/pgx/stdlib" "gemma.intevation.de/gemma/pkg/config" ) var ( // ErrNoMetamorphUser is returned if no metamorphic user is configured. ErrNoMetamorphUser = errors.New("no metamorphic user configured") // ErrNotLoggedIn is returned if there is the user is not logged in. ErrNotLoggedIn = errors.New("not logged in") ) // OpenDB opens up a database connection with a given username and password. // The other credentials are taken from the configuration. func OpenDB(user, password string) (*sql.DB, error) { // To ease SSL config ride a bit on parsing. cc, err := pgx.ParseConnectionString("sslmode=" + config.DBSSLMode()) if err != nil { return nil, err } // Do the rest manually to allow whitespace in user/password. cc.Host = config.DBHost() cc.Port = uint16(config.DBPort()) cc.User = user cc.Password = password cc.Database = config.DBName() return stdlib.OpenDB(cc), nil } type metamorph struct { sync.Mutex db *sql.DB } var mm metamorph func (m *metamorph) open() (*sql.DB, error) { m.Lock() defer m.Unlock() if m.db != nil { return m.db, nil } user := config.DBUser() if user == "" { return nil, ErrNoMetamorphUser } db, err := OpenDB(user, config.DBPassword()) if err != nil { return nil, err } m.db = db return db, nil } func metamorphConn(ctx context.Context, user string) (*sql.Conn, error) { db, err := mm.open() if err != nil { return nil, err } conn, err := db.Conn(ctx) if err != nil { return nil, err } if _, err := conn.ExecContext(ctx, `SELECT public.setrole_plan($1)`, user); err != nil { conn.Close() return nil, err } return conn, nil } const allRoles = ` WITH RECURSIVE cte AS ( SELECT oid FROM pg_roles WHERE rolname = current_user UNION ALL SELECT m.roleid FROM cte JOIN pg_auth_members m ON m.member = cte.oid ) SELECT rolname FROM pg_roles WHERE oid IN (SELECT oid FROM cte) AND rolname <> current_user AND EXISTS (SELECT 1 FROM users.list_users WHERE username = current_user AND active)` // AllOtherRoles loggs in as user with password and returns a list // of all roles the logged in user has in the system. func AllOtherRoles(user, password string) (Roles, error) { db, err := OpenDB(user, password) if err != nil { return nil, err } defer db.Close() rows, err := db.Query(allRoles) if err != nil { return nil, err } defer rows.Close() roles := Roles{} // explicit empty by intention. for rows.Next() { var role string if err := rows.Scan(&role); err != nil { return nil, err } roles = append(roles, role) } return roles, rows.Err() } // RunAs runs a given function fn with a database connection impersonated // as the given role. // To make this work a metamorphic user has to be configured in // the system configuration. func RunAs(ctx context.Context, role string, fn func(*sql.Conn) error) error { conn, err := metamorphConn(ctx, role) if err != nil { return err } defer conn.Close() return fn(conn) } // RunAllAs runs the given functions fns with a database connection impersonated // as the given role. // To make this work a metamorphic user has to be configured in // the system configuration. func RunAllAs(ctx context.Context, role string, fns ...func(*sql.Conn) error) error { conn, err := metamorphConn(ctx, role) if err != nil { return err } defer conn.Close() for _, fn := range fns { if err := fn(conn); err != nil { return err } } return nil } // RunAsSessionUser is a convinience wrapper araound which extracts // the logged in user from a session and calls RunAs with it. func RunAsSessionUser(req *http.Request, fn func(*sql.Conn) error) error { token, ok := GetToken(req) if !ok { return ErrNotLoggedIn } session := Sessions.Session(token) if session == nil { return ErrNotLoggedIn } return RunAs(req.Context(), session.User, fn) }