Mercurial > gemma
view pkg/controllers/user.go @ 5560:f2204f91d286
Join the log lines of imports to the log exports to recover data from them.
Used in SR export to extract information that where in the meta json
but now are only found in the log.
author | Sascha L. Teichmann <sascha.teichmann@intevation.de> |
---|---|
date | Wed, 09 Feb 2022 18:34:40 +0100 |
parents | 79155213c4da |
children | 6270951dda28 |
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> // * Tom Gottfried <tom.gottfried@intevation.de> // * Sascha Wilde <sascha.wilde@intevation.de> package controllers import ( "bytes" "database/sql" "fmt" "net/http" "strconv" "strings" "text/template" "time" "github.com/gorilla/mux" "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/log" "gemma.intevation.de/gemma/pkg/misc" "gemma.intevation.de/gemma/pkg/models" "gemma.intevation.de/gemma/pkg/pgxutils" "gemma.intevation.de/gemma/pkg/scheduler" mw "gemma.intevation.de/gemma/pkg/middleware" ) const ( createUserSQL = `INSERT INTO users.list_users VALUES ($1, $2, $3, $4, NULL, $5, $6, true)` createUserExtentSQL = `INSERT INTO users.list_users VALUES ($1, $2, $3, $4, ST_MakeBox2D(ST_Point($5, $6), ST_Point($7, $8)), $9, $10, true)` updateUserUnprivSQL = `UPDATE users.list_users SET (pw, map_extent, email_address) = ($2, ST_MakeBox2D(ST_Point($3, $4), ST_Point($5, $6)), $7) WHERE username = $1` updateUserSQL = `UPDATE users.list_users SET (rolname, username, pw, country, map_extent, email_address, report_reciever) = ($2, $3, $4, $5, NULL, $6, $7) WHERE username = $1` updateUserExtentSQL = `UPDATE users.list_users SET (rolname, username, pw, country, map_extent, email_address, report_reciever) = ($2, $3, $4, $5, ST_MakeBox2D(ST_Point($6, $7), ST_Point($8, $9)), $10, $11) WHERE username = $1` deleteUserSQL = ` WITH del AS ( DELETE FROM users.list_users WHERE username = $1 AND ( rolname NOT IN ('waterway_admin', 'sys_admin') OR NOT EXISTS (SELECT 1 FROM import.imports WHERE username = $1)) RETURNING * ), up AS ( UPDATE users.list_users SET (email_address, report_reciever, active) = ('nomail@example.com', false, false) WHERE username = $1 AND NOT EXISTS (SELECT * FROM del) RETURNING * ) SELECT EXISTS (SELECT * FROM del) AS deleted, EXISTS (SELECT * FROM up) AS updated` listUsersSQL = `SELECT rolname, username, country, email_address, ST_XMin(map_extent), ST_YMin(map_extent), ST_XMax(map_extent), ST_YMax(map_extent), report_reciever, active FROM users.list_users` listUserSQL = `SELECT rolname, country, email_address, ST_XMin(map_extent), ST_YMin(map_extent), ST_XMax(map_extent), ST_YMax(map_extent), report_reciever FROM users.list_users WHERE username = $1` ) var ( testSysadminNotifyMailTmpl = template.Must( template.New("sysadmin").Parse(`Dear {{ .User }}, this is a test email from the Gemma System Errors notification service. You recieved this mail, because a System Administrator triggered the test mail sending function at {{ .Timestamp }}. When a critical system error is detected an automated mail will be sent to {{ .Email }} with details on the error condition.`)) testWWAdminNotifyMailTmpl = template.Must( template.New("waterwayadmin").Parse(`Dear {{ .User }}, this is a test email from the Gemma System Mail notification service. You recieved this mail, because a System Administrator triggered the test mail sending function at {{ .Timestamp }}. When the status of a data import managed by you changes an automated mail will be sent to {{ .Email }} with details on the new import status (including import errors) and details on the corresponding import.`)) testWWUserNotifyMailTmpl = template.Must( template.New("waterwayuser").Parse(`Dear {{ .User }}, this is a test email from the Gemma System Mail notification service. You recieved this mail, because a System Administrator triggered the test mail sending function at {{ .Timestamp }}.`)) ) func deleteUser(req *http.Request) (jr mw.JSONResult, err error) { user := mux.Vars(req)["user"] if !models.UserName(user).IsValid() { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: user invalid", } return } session, _ := auth.GetSession(req) if session.User == user { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: cannot delete yourself", } return } ctx := req.Context() db := mw.JSONConn(req) // Remove scheduled tasks. ids, err2 := scheduler.ScheduledUserIDs(ctx, db, user) if err2 == nil { if len(ids) > 0 { go func() { scheduler.UnbindByIDs(ids) }() } } else { log.Errorf("%v\n", err2) } var deleted, updated bool if err = db.QueryRowContext(ctx, deleteUserSQL, user).Scan(&deleted, &updated); err != nil { return } var action string switch { case !deleted && !updated: err = mw.JSONError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Cannot find user %s.", user), } return case deleted: action = "deleted" case updated: action = "deactivated" default: log.Errorf("Should not happen: user '%s' updated and deleted.\n", user) action = "deleted" } // Running in a go routine should not be necessary. go func() { auth.Sessions.Logout(user) }() log.Infof("User '%s' %s.\n", user, action) jr = mw.JSONResult{ Result: struct { Action string `json:"action"` }{ Action: action, }, } return } func updateUser(req *http.Request) (jr mw.JSONResult, err error) { user := models.UserName(mux.Vars(req)["user"]) if !user.IsValid() { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: user invalid", } return } newUser := mw.JSONInput(req).(*models.User) var res sql.Result db := mw.JSONConn(req) if s, _ := auth.GetSession(req); s.Roles.Has("sys_admin") { if newUser.Extent == nil { res, err = db.ExecContext( req.Context(), updateUserSQL, user, newUser.Role, newUser.User, newUser.Password, newUser.Country, newUser.Email, newUser.Reports, ) } else { res, err = db.ExecContext( req.Context(), updateUserExtentSQL, user, newUser.Role, newUser.User, newUser.Password, newUser.Country, newUser.Extent.X1, newUser.Extent.Y1, newUser.Extent.X2, newUser.Extent.Y2, newUser.Email, newUser.Reports, ) } } else { if newUser.Extent == nil { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "extent is mandatory", } return } res, err = db.ExecContext( req.Context(), updateUserUnprivSQL, user, newUser.Password, newUser.Extent.X1, newUser.Extent.Y1, newUser.Extent.X2, newUser.Extent.Y2, newUser.Email, ) } if err != nil { return } if n, err2 := res.RowsAffected(); err2 == nil && n == 0 { err = mw.JSONError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Cannot find user %s.", user), } return } if user != newUser.User { // Running in a go routine should not be necessary. go func() { auth.Sessions.Logout(string(user)) }() } log.Infof("User '%s' updated.\n", user) jr = mw.JSONResult{ Code: http.StatusCreated, Result: struct { Result string `json:"result"` }{"success"}, } return } func patchUser(req *http.Request) (jr mw.JSONResult, err error) { user := models.UserName(mux.Vars(req)["user"]) if !user.IsValid() { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: user invalid", } return } s, ok := auth.GetSession(req) if !ok { err = mw.JSONError{ Code: http.StatusUnauthorized, Message: "error: not logged in", } return } priv := s.Roles.Has("sys_admin") if !priv && s.User != string(user) { err = mw.JSONError{ Code: http.StatusUnauthorized, Message: "error: not allowed to modify someone else", } return } var ( columns []string positions []string args []interface{} ) update := func(column string, value interface{}) { columns = append(columns, column) positions = append(positions, "$"+strconv.Itoa(len(positions)+1)) args = append(args, value) } updateBox := func(column string, extent *models.BoundingBox) { columns = append(columns, column) pos := len(positions) position := fmt.Sprintf("ST_MakeBox2D(ST_Point($%d, $%d), ST_Point($%d, $%d))", pos+1, pos+2, pos+3, pos+4) positions = append(positions, position) args = append(args, extent.X1, extent.Y1, extent.X2, extent.Y2) } patch := mw.JSONInput(req).(*models.UserPatch) if patch.User != nil && priv { update("user", *patch.User) } if patch.Role != nil && priv { update("rolname", *patch.Role) } if patch.Password != nil { update("pw", *patch.Password) } if patch.Email != nil { update("email_address", *patch.Email) } if patch.Country != nil && priv { update("country", *patch.Country) } if patch.Reports != nil && priv { update("report_reciever", *patch.Reports) } if patch.Active != nil && priv { update("active", *patch.Active) } if patch.Extent != nil { updateBox("map_extent", patch.Extent) } var colsS, posS string switch len(columns) { case 0: // Nothing to do jr = mw.JSONResult{ Code: http.StatusCreated, Result: struct { Result string `json:"result"` }{"success"}, } return case 1: // No brackets if there is only one argument. colsS = columns[0] posS = positions[0] default: colsS = "(" + strings.Join(columns, ",") + ")" posS = "(" + strings.Join(positions, ",") + ")" } stmt := fmt.Sprintf( `UPDATE users.list_users SET %s = %s WHERE username = $%d`, colsS, posS, len(positions)+1) args = append(args, user) db := mw.JSONConn(req) var res sql.Result if res, err = db.ExecContext(req.Context(), stmt, args...); err != nil { return } if n, err2 := res.RowsAffected(); err2 == nil && n == 0 { err = mw.JSONError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Cannot find user %s.", user), } return } if patch.User != nil && *patch.User != user { // Running in a go routine should not be necessary. go func() { auth.Sessions.Logout(string(user)) }() } // Log if the user should be reactivated, even if he is already active. var reactivated string if patch.Active != nil && *patch.Active && priv { reactivated = " (reactivation requested)" } log.Infof("User '%s' modified%s.\n", user, reactivated) jr = mw.JSONResult{ Code: http.StatusCreated, Result: struct { Result string `json:"result"` }{"success"}, } return } func createUser(req *http.Request) (jr mw.JSONResult, err error) { user := mw.JSONInput(req).(*models.User) db := mw.JSONConn(req) if user.Extent == nil { _, err = db.ExecContext( req.Context(), createUserSQL, user.Role, user.User, user.Password, user.Country, user.Email, user.Reports, ) } else { _, err = db.ExecContext( req.Context(), createUserExtentSQL, user.Role, user.User, user.Password, user.Country, user.Extent.X1, user.Extent.Y1, user.Extent.X2, user.Extent.Y2, user.Email, user.Reports, ) } if err != nil { m, c := pgxutils.ReadableError{Err: err}.MessageAndCode() err = mw.JSONError{Code: c, Message: m} return } log.Infof("User '%s' created.\n", user.User) jr = mw.JSONResult{ Code: http.StatusCreated, Result: struct { Result string `json:"result"` }{"success"}, } return } func listUsers(req *http.Request) (jr mw.JSONResult, err error) { var rows *sql.Rows rows, err = mw.JSONConn(req).QueryContext(req.Context(), listUsersSQL) if err != nil { return } defer rows.Close() var users []*models.User for rows.Next() { user := &models.User{Extent: &models.BoundingBox{}} if err = rows.Scan( &user.Role, &user.User, &user.Country, &user.Email, &user.Extent.X1, &user.Extent.Y1, &user.Extent.X2, &user.Extent.Y2, &user.Reports, &user.Active, ); err != nil { return } users = append(users, user) } jr = mw.JSONResult{ Result: struct { Users []*models.User `json:"users"` }{users}, } return } func listUser(req *http.Request) (jr mw.JSONResult, err error) { user := models.UserName(mux.Vars(req)["user"]) if !user.IsValid() { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: user invalid", } return } result := &models.User{ User: user, Extent: &models.BoundingBox{}, } err = mw.JSONConn(req).QueryRowContext(req.Context(), listUserSQL, user).Scan( &result.Role, &result.Country, &result.Email, &result.Extent.X1, &result.Extent.Y1, &result.Extent.X2, &result.Extent.Y2, &result.Reports, ) switch { case err == sql.ErrNoRows: err = mw.JSONError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Cannot find user %s.", user), } return case err != nil: return } jr.Result = result return } func sendTestMail(req *http.Request) (jr mw.JSONResult, err error) { user := models.UserName(mux.Vars(req)["user"]) if !user.IsValid() { err = mw.JSONError{ Code: http.StatusBadRequest, Message: "error: user invalid", } return } userData := &models.User{ User: user, Extent: &models.BoundingBox{}, } err = mw.JSONConn(req).QueryRowContext(req.Context(), listUserSQL, user).Scan( &userData.Role, &userData.Country, &userData.Email, &userData.Extent.X1, &userData.Extent.Y1, &userData.Extent.X2, &userData.Extent.Y2, &userData.Reports, ) switch { case err == sql.ErrNoRows: err = mw.JSONError{ Code: http.StatusNotFound, Message: fmt.Sprintf("Cannot find user %s.", user), } return case err != nil: return } var subject string var tmplVars = struct { User string Timestamp string Email string }{ User: string(user), Timestamp: time.Now().Format("2006-01-02 15:04:05"), Email: string(userData.Email), } var bodyTmpl *template.Template switch userData.Role { case "sys_admin": subject = "Gemma: Sysadmin Notification TEST" bodyTmpl = testSysadminNotifyMailTmpl case "waterway_admin": subject = "Gemma: Waterway Admin Notification TEST" bodyTmpl = testWWAdminNotifyMailTmpl default: subject = "Gemma: Waterway User Notification TEST" bodyTmpl = testWWUserNotifyMailTmpl } var buf bytes.Buffer if err := bodyTmpl.Execute(&buf, &tmplVars); err != nil { log.Errorf("%v\n", err) } err = misc.SendMail(string(userData.Email), subject, buf.String()) if err != nil { return } jr.Result = &struct { Message string `json:"message"` }{"Sending test mail."} return }