Mercurial > gemma
changeset 452:1ebc174a1c53
Merge, because not having pulled earlier.
author | Bernhard Reiter <bernhard@intevation.de> |
---|---|
date | Wed, 22 Aug 2018 10:04:52 +0200 |
parents | ed615a62466c (current diff) a74b8c2a4e75 (diff) |
children | a7dc68d8e22f |
files | pkg/controllers/types.go |
diffstat | 17 files changed, 479 insertions(+), 271 deletions(-) [+] |
line wrap: on
line diff
--- a/cmd/gemma/geoserver.go Wed Aug 22 10:04:04 2018 +0200 +++ b/cmd/gemma/geoserver.go Wed Aug 22 10:04:52 2018 +0200 @@ -10,6 +10,7 @@ "gemma.intevation.de/gemma/pkg/config" "gemma.intevation.de/gemma/pkg/misc" + "gemma.intevation.de/gemma/pkg/models" ) const ( @@ -179,9 +180,9 @@ user = config.GeoServerUser() password = config.GeoServerPassword() auth = basicAuth(user, password) - tables = config.GeoServerTables() ) + tables := models.PublishedServices.Filter(models.PublishedWFS) if len(tables) == 0 { log.Println("info: no tables to publish") return nil @@ -233,7 +234,9 @@ hasFeature = func(string) bool { return false } } - for _, table := range tables { + for i := range tables { + table := tables[i].Name + if hasFeature(table) { log.Printf("info: featuretype %s already exists.\n", table) continue @@ -286,7 +289,7 @@ func prepareGeoServer() error { if config.MetamorphDBUser() == "" { - log.Panicln("info: Need metamorphic db user to configure GeoServer") + log.Println("info: Need metamorphic db user to configure GeoServer") return nil }
--- a/pkg/auth/middleware.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/auth/middleware.go Wed Aug 22 10:04:52 2018 +0200 @@ -63,12 +63,7 @@ func HasRole(roles ...string) func(*Session) bool { return func(session *Session) bool { - for _, r1 := range roles { - if session.Roles.Has(r1) { - return true - } - } - return false + return session.Roles.HasAny(roles...) } }
--- a/pkg/auth/opendb.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/auth/opendb.go Wed Aug 22 10:04:52 2018 +0200 @@ -2,6 +2,8 @@ import ( "database/sql" + "errors" + "strings" "github.com/jackc/pgx" "github.com/jackc/pgx/stdlib" @@ -38,7 +40,11 @@ SELECT rolname FROM pg_roles WHERE oid IN (SELECT oid FROM cte) AND rolname <> current_user` -func AllOtherRoles(user, password string) ([]string, error) { +const InvalidRoleCharacters = `\"':;` + +var ErrInvalidRoleCharacters = errors.New("rolename contains invalid character") + +func AllOtherRoles(user, password string) (Roles, error) { db, err := OpenDB(user, password) if err != nil { return nil, err @@ -50,7 +56,7 @@ } defer rows.Close() - roles := []string{} // explicit empty by intention. + roles := Roles{} // explicit empty by intention. for rows.Next() { var role string @@ -61,3 +67,18 @@ } return roles, rows.Err() } + +func RunAs(role string, fn func(*sql.DB) error) error { + if strings.Contains(role, InvalidRoleCharacters) { + return ErrInvalidRoleCharacters + } + db, err := OpenDB(config.MetamorphDBUser(), config.MetamorhpDBPassword()) + if err != nil { + return nil + } + defer db.Close() + if _, err := db.Exec(`SET ROLE "` + role + `"`); err != nil { + return err + } + return fn(db) +}
--- a/pkg/auth/session.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/auth/session.go Wed Aug 22 10:04:52 2018 +0200 @@ -2,6 +2,7 @@ import ( "encoding/base64" + "errors" "io" "time" @@ -27,12 +28,21 @@ return false } +func (r Roles) HasAny(roles ...string) bool { + for _, y := range roles { + if r.Has(y) { + return true + } + } + return false +} + const ( sessionKeyLength = 20 maxTokenValid = time.Hour * 3 ) -func NewSession(user, password string, roles []string) *Session { +func NewSession(user, password string, roles Roles) *Session { // Create the Claims return &Session{ @@ -78,11 +88,16 @@ common.GenerateRandomKey(sessionKeyLength)) } +var ErrInvalidRole = errors.New("Invalid role") + func GenerateSession(user, password string) (string, *Session, error) { roles, err := AllOtherRoles(user, password) if err != nil { return "", nil, err } + if !roles.HasAny("sys_admin", "waterway_admin", "waterway_user") { + return "", nil, ErrInvalidRole + } token := GenerateSessionKey() session := NewSession(user, password, roles) ConnPool.Add(token, session)
--- a/pkg/config/config.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/config/config.go Wed Aug 22 10:04:52 2018 +0200 @@ -27,9 +27,6 @@ func WebHost() string { return viper.GetString("host") } func WebPort() uint { return uint(viper.GetInt32("port")) } -func ServiceUser() string { return viper.GetString("service-user") } -func ServicePassword() string { return viper.GetString("service-password") } - func MetamorphDBUser() string { return viper.GetString("metamorph-db-user") } func MetamorhpDBPassword() string { return viper.GetString("metamorph-db-password") } @@ -47,7 +44,6 @@ func GeoServerURL() string { return viper.GetString("geoserver-url") } func GeoServerUser() string { return viper.GetString("geoserver-user") } func GeoServerPassword() string { return viper.GetString("geoserver-password") } -func GeoServerTables() []string { return viper.GetStringSlice("geoserver-tables") } var ( proxyKeyOnce sync.Once @@ -92,10 +88,6 @@ // TODO: Fill me! } -var geoTables = []string{ - "fairway_dimensions", -} - func init() { cobra.OnInitialize(initConfig) fl := RootCmd.PersistentFlags() @@ -135,9 +127,6 @@ strP("host", "o", "localhost", "host of the web app") uiP("port", "p", 8000, "port of the web app") - str("service-user", "postgres", "user to do service tasks") - str("service-password", "", "password of user to do service tasks") - str("mail-host", "localhost", "server to send mail with") ui("mail-port", 465, "port of server to send mail with") str("mail-user", "gemma", "user to send mail with") @@ -150,7 +139,6 @@ str("geoserver-url", "http://localhost:8080/geoserver", "URL to GeoServer") str("geoserver-user", "admin", "GeoServer user") str("geoserver-password", "geoserver", "GeoServer password") - strSl("geoserver-tables", geoTables, "tables to publish with GeoServer") str("metamorph-db-user", "", "Metamorphic database user") str("metamorph-db-password", "", "Metamorphic database user password")
--- a/pkg/controllers/proxy.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/controllers/proxy.go Wed Aug 22 10:04:52 2018 +0200 @@ -35,29 +35,6 @@ "http://schemas.opengis.net/gml": struct{}{}, } -func findProxy(key string) func(string) (string, bool) { - entries := config.Proxies(key) - return func(entry string) (string, bool) { - if entries == nil || len(entries) == 0 { - return "", false - } - alias, found := entries[entry] - if !found { - return "", false - } - data, ok := alias.(map[string]interface{}) - if !ok { - return "", false - } - urlS, found := data["url"] - if !found { - return "", false - } - url, ok := urlS.(string) - return url, ok - } -} - func proxyDirector(lookup func(string) (string, bool)) func(*http.Request) { return func(req *http.Request) {
--- a/pkg/controllers/pwreset.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/controllers/pwreset.go Wed Aug 22 10:04:52 2018 +0200 @@ -15,8 +15,8 @@ "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/common" - "gemma.intevation.de/gemma/pkg/config" "gemma.intevation.de/gemma/pkg/misc" + "gemma.intevation.de/gemma/pkg/models" ) const ( @@ -55,6 +55,8 @@ cleanupPause = 15 * time.Minute ) +const pwResetRole = "pw_reset" + var ( passwordResetRequestMailTmpl = template.Must( template.New("request").Parse(`You have requested a password change @@ -83,15 +85,6 @@ Your service team`)) ) -func asServiceUser(fn func(*sql.DB) error) error { - db, err := auth.OpenDB(config.ServiceUser(), config.ServicePassword()) - if err == nil { - defer db.Close() - err = fn(db) - } - return err -} - func init() { go removeOutdated() } @@ -99,7 +92,7 @@ func removeOutdated() { for { time.Sleep(cleanupPause) - err := asServiceUser(func(db *sql.DB) error { + err := auth.RunAs(pwResetRole, func(db *sql.DB) error { good := time.Now().Add(-passwordResetValid) _, err := db.Exec(cleanupRequestsSQL, good) return err @@ -175,7 +168,7 @@ _ *sql.DB, ) (jr JSONResult, err error) { - user := input.(*PWResetUser) + user := input.(*models.PWResetUser) if user.User == "" { err = JSONError{http.StatusBadRequest, "Invalid user name"} @@ -184,7 +177,7 @@ var hash, email string - if err = asServiceUser(func(db *sql.DB) error { + if err = auth.RunAs(pwResetRole, func(db *sql.DB) error { var count int64 if err := db.QueryRow(countRequestsSQL).Scan(&count); err != nil { @@ -249,7 +242,7 @@ var email, user, password string - if err = asServiceUser(func(db *sql.DB) error { + if err = auth.RunAs(pwResetRole, func(db *sql.DB) error { err := db.QueryRow(findRequestSQL, hash).Scan(&email, &user) switch { case err == sql.ErrNoRows:
--- a/pkg/controllers/routes.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/controllers/routes.go Wed Aug 22 10:04:52 2018 +0200 @@ -8,6 +8,7 @@ "gemma.intevation.de/gemma/pkg/auth" "gemma.intevation.de/gemma/pkg/middleware" + "gemma.intevation.de/gemma/pkg/models" ) func BindRoutes(m *mux.Router) { @@ -25,7 +26,7 @@ })).Methods(http.MethodGet) api.Handle("/users", sysAdmin(&JSONHandler{ - Input: func() interface{} { return new(User) }, + Input: func() interface{} { return new(models.User) }, Handle: createUser, })).Methods(http.MethodPost) @@ -34,7 +35,7 @@ })).Methods(http.MethodGet) api.Handle("/users/{user}", all(&JSONHandler{ - Input: func() interface{} { return new(User) }, + Input: func() interface{} { return new(models.User) }, Handle: updateUser, })).Methods(http.MethodPut) @@ -44,7 +45,7 @@ // Password resets. api.Handle("/users/passwordreset", &JSONHandler{ - Input: func() interface{} { return new(PWResetUser) }, + Input: func() interface{} { return new(models.PWResetUser) }, Handle: passwordResetRequest, }).Methods(http.MethodPost) @@ -53,24 +54,26 @@ }).Methods(http.MethodGet) // External proxies. - proxy := &httputil.ReverseProxy{ - Director: proxyDirector(findProxy("external")), + external := &httputil.ReverseProxy{ + Director: proxyDirector(models.ExternalServices.Find), ModifyResponse: proxyModifyResponse("/api/external/"), } - api.Handle("/external/{hash}/{url}", proxy). + externalAuth := all(external) + + api.Handle("/external/{hash}/{url}", externalAuth). Methods( http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete) - api.Handle("/external/{entry}", proxy). + api.Handle("/external/{entry}", externalAuth). Methods( http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete) // Internal proxies. internal := &httputil.ReverseProxy{ - Director: proxyDirector(findProxy("internal")), + Director: proxyDirector(models.PublishedServices.Find), ModifyResponse: proxyModifyResponse("/api/internal/"), }
--- a/pkg/controllers/token.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/controllers/token.go Wed Aug 22 10:04:52 2018 +0200 @@ -7,6 +7,7 @@ "net/http" "gemma.intevation.de/gemma/pkg/auth" + "gemma.intevation.de/gemma/pkg/models" ) func sendJSON(rw http.ResponseWriter, data interface{}) { @@ -63,7 +64,7 @@ password = req.FormValue("password") ) - if !UserName(user).isValid() || password == "" { + if !models.UserName(user).IsValid() || password == "" { http.Error(rw, "Invalid credentials", http.StatusBadRequest) return }
--- a/pkg/controllers/types.go Wed Aug 22 10:04:04 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -package controllers - -import ( - "database/sql/driver" - "encoding/json" - "errors" - "regexp" - "strings" -) - -type ( - Email string - Country string - Role string - UserName string - - BoundingBox struct { - X1 float64 `json:"x1"` - Y1 float64 `json:"y1"` - X2 float64 `json:"x2"` - Y2 float64 `json:"y2"` - } - - User struct { - User UserName `json:"user"` - Role Role `json:"role"` - Password string `json:"password,omitempty"` - Email Email `json:"email"` - Country Country `json:"country"` - Extent *BoundingBox `json:"extent"` - } - - PWResetUser struct { - User string `json:"user"` - } -) - -var ( - // https://stackoverflow.com/questions/201323/how-to-validate-an-email-address-using-a-regular-expression - emailRe = regexp.MustCompile( - `(?:[a-z0-9!#$%&'*+/=?^_` + "`" + - `{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_` + "`" + - `{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]` + - `|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")` + - `@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?` + - `|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}` + - `(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]` + - `:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]` + - `|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])`) - - errNoEmailAddress = errors.New("Not a valid email address") - errNoString = errors.New("Not a string") -) - -func (e *Email) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - if !emailRe.MatchString(s) { - return errNoEmailAddress - } - *e = Email(s) - return nil -} - -func (e Email) Value() (driver.Value, error) { - return string(e), nil -} - -func (e *Email) Scan(src interface{}) (err error) { - if s, ok := src.(string); ok { - *e = Email(s) - } else { - err = errNoString - } - return -} - -var errNoValidUser = errors.New("Not a valid user") - -func (u UserName) isValid() bool { - return u != "" && !strings.ContainsAny(string(u), `\"':;`) -} - -func (u *UserName) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - if !emailRe.MatchString(s) { - return errNoEmailAddress - } - user := UserName(s) - if !user.isValid() { - return errNoValidUser - } - *u = user - return nil -} - -func (u *UserName) Scan(src interface{}) (err error) { - if s, ok := src.(string); ok { - *u = UserName(s) - } else { - err = errNoString - } - return -} - -var ( - validCountries = []string{ - "AT", "BG", "DE", "HU", "HR", - "MD", "RO", "RS", "SK", "UA", - } - errNoValidCountry = errors.New("Not a valid country") -) - -func (c *Country) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - s = strings.ToUpper(s) - for _, v := range validCountries { - if v == s { - *c = Country(v) - return nil - } - } - return errNoValidCountry -} - -func (c Country) Value() (driver.Value, error) { - return string(c), nil -} - -func (c *Country) Scan(src interface{}) (err error) { - if s, ok := src.(string); ok { - *c = Country(s) - } else { - err = errNoString - } - return -} - -var ( - validRoles = []string{ - "waterway_user", - "waterway_admin", - "sys_admin", - } - errNoValidRole = errors.New("Not a valid role") -) - -func (r Role) Value() (driver.Value, error) { - return string(r), nil -} - -func (r *Role) Scan(src interface{}) (err error) { - if s, ok := src.(string); ok { - *r = Role(s) - } else { - err = errNoString - } - return -} - -func (r *Role) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - s = strings.ToLower(s) - for _, v := range validRoles { - if v == s { - *r = Role(v) - return nil - } - } - return errNoValidRole -}
--- a/pkg/controllers/user.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/controllers/user.go Wed Aug 22 10:04:52 2018 +0200 @@ -8,6 +8,7 @@ "github.com/gorilla/mux" "gemma.intevation.de/gemma/pkg/auth" + "gemma.intevation.de/gemma/pkg/models" ) const ( @@ -57,7 +58,7 @@ ) (jr JSONResult, err error) { user := mux.Vars(req)["user"] - if !UserName(user).isValid() { + if !models.UserName(user).IsValid() { err = JSONError{http.StatusBadRequest, "error: user invalid"} return } @@ -94,13 +95,13 @@ db *sql.DB, ) (jr JSONResult, err error) { - user := UserName(mux.Vars(req)["user"]) - if !user.isValid() { + user := models.UserName(mux.Vars(req)["user"]) + if !user.IsValid() { err = JSONError{http.StatusBadRequest, "error: user invalid"} return } - newUser := input.(*User) + newUser := input.(*models.User) var res sql.Result if s, _ := auth.GetSession(req); s.Roles.Has("sys_admin") { @@ -173,7 +174,7 @@ db *sql.DB, ) (jr JSONResult, err error) { - user := input.(*User) + user := input.(*models.User) if user.Extent == nil { _, err = db.Exec( @@ -223,10 +224,10 @@ } defer rows.Close() - var users []*User + var users []*models.User for rows.Next() { - user := &User{Extent: &BoundingBox{}} + user := &models.User{Extent: &models.BoundingBox{}} if err = rows.Scan( &user.Role, &user.User, @@ -242,7 +243,7 @@ jr = JSONResult{ Result: struct { - Users []*User `json:"users"` + Users []*models.User `json:"users"` }{users}, } return @@ -253,15 +254,15 @@ db *sql.DB, ) (jr JSONResult, err error) { - user := UserName(mux.Vars(req)["user"]) - if !user.isValid() { + user := models.UserName(mux.Vars(req)["user"]) + if !user.IsValid() { err = JSONError{http.StatusBadRequest, "error: user invalid"} return } - result := &User{ + result := &models.User{ User: user, - Extent: &BoundingBox{}, + Extent: &models.BoundingBox{}, } err = db.QueryRow(listUserSQL, user).Scan(
--- a/pkg/middleware/modifyquery.go Wed Aug 22 10:04:04 2018 +0200 +++ b/pkg/middleware/modifyquery.go Wed Aug 22 10:04:52 2018 +0200 @@ -81,7 +81,7 @@ parameters.Del("env") session, ok := auth.GetSession(req) - if ok && !strings.ContainsAny(session.User, `\"':;`) { + if ok && !strings.ContainsAny(session.User, auth.InvalidRoleCharacters) { log.Printf("Injecting user %s\n", session.User) parameters.Set("env", "user:"+session.User) }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/models/extservices.go Wed Aug 22 10:04:52 2018 +0200 @@ -0,0 +1,96 @@ +package models + +import ( + "database/sql" + "log" + "sort" + "sync" + + "gemma.intevation.de/gemma/pkg/auth" +) + +type ExtEntry struct { + Name string + URL string + WFS bool +} + +type ExtServices struct { + mu sync.Mutex + entries []ExtEntry +} + +var ExternalServices = &ExtServices{} + +const selectExternalServices = `SELECT local_name, remote_url, is_wfs +FROM sys_admin.external_services ORDER BY local_name` + +func (es *ExtServices) Find(name string) (string, bool) { + es.mu.Lock() + defer es.mu.Unlock() + + if es.entries == nil { + if err := es.load(); err != nil { + log.Printf("error: %v\n", err) + return "", false + } + } + n := sort.Search(len(es.entries), func(i int) bool { + return es.entries[i].Name >= name + }) + if n == len(es.entries) || es.entries[n].Name != name { + return "", false + } + return es.entries[n].URL, true +} + +func (es *ExtServices) load() error { + // make empty slice to prevent retry if slice is empty. + es.entries = []ExtEntry{} + return auth.RunAs("sys_admin", func(db *sql.DB) error { + rows, err := db.Query(selectExternalServices) + if err != nil { + return err + } + defer rows.Close() + for rows.Next() { + var entry ExtEntry + if err := rows.Scan( + &entry.Name, + &entry.URL, + &entry.WFS, + ); err != nil { + return err + } + es.entries = append(es.entries, entry) + } + return rows.Err() + }) +} + +func (es *ExtServices) Invalidate() { + es.mu.Lock() + es.entries = nil + es.mu.Unlock() +} + +func ExternalWMS(entry PubEntry) bool { return !entry.WFS } +func ExternalWFS(entry PubEntry) bool { return entry.WFS } + +func (es *ExtServices) Filter(accept func(ExtEntry) bool) []ExtEntry { + es.mu.Lock() + defer es.mu.Unlock() + if es.entries == nil { + if err := es.load(); err != nil { + log.Printf("error: %v\n", err) + return nil + } + } + ee := make([]ExtEntry, 0, len(es.entries)) + for _, e := range es.entries { + if accept(e) { + ee = append(ee, e) + } + } + return ee +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/models/pubservices.go Wed Aug 22 10:04:52 2018 +0200 @@ -0,0 +1,99 @@ +package models + +import ( + "database/sql" + "log" + "sort" + "sync" + + "gemma.intevation.de/gemma/pkg/auth" +) + +type PubEntry struct { + Name string + Style sql.NullString + WMS bool + WFS bool +} + +type PubServices struct { + entries []PubEntry + mu sync.Mutex +} + +const selectPublishedServices = `SELECT name, style, as_wms, as_wfs +FROM sys_admin.published_services ORDER by name` + +var PublishedServices = &PubServices{} + +func (ps *PubServices) Find(name string) (string, bool) { + ps.mu.Lock() + defer ps.mu.Unlock() + + if ps.entries == nil { + if err := ps.load(); err != nil { + log.Printf("error: %v\n", err) + return "", false + } + } + + n := sort.Search(len(ps.entries), func(i int) bool { + return ps.entries[i].Name >= name + }) + if n == len(ps.entries) || ps.entries[n].Name != name { + return "", false + } + return name, true +} + +func (ps *PubServices) load() error { + // make empty slice to prevent retry if slice is empty. + ps.entries = []PubEntry{} + return auth.RunAs("sys_admin", func(db *sql.DB) error { + rows, err := db.Query(selectPublishedServices) + if err != nil { + return err + } + defer rows.Close() + for rows.Next() { + var entry PubEntry + if err := rows.Scan( + &entry.Name, &entry.Style, + &entry.WFS, &entry.WFS, + ); err != nil { + return err + } + ps.entries = append(ps.entries, entry) + } + return rows.Err() + }) + return nil +} + +func (ps *PubServices) Invalidate() { + ps.mu.Lock() + ps.entries = nil + ps.mu.Unlock() +} + +func PublishedWMS(entry PubEntry) bool { return entry.WMS } +func PublishedWFS(entry PubEntry) bool { return entry.WFS } + +func (ps *PubServices) Filter(accept func(PubEntry) bool) []PubEntry { + ps.mu.Lock() + defer ps.mu.Unlock() + if ps.entries == nil { + if err := ps.load(); err != nil { + log.Printf("error: %v\n", err) + return nil + } + } + pe := make([]PubEntry, 0, len(ps.entries)) + for _, e := range ps.entries { + if accept(e) { + pe = append(pe, e) + } + } + + return pe +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pkg/models/types.go Wed Aug 22 10:04:52 2018 +0200 @@ -0,0 +1,185 @@ +package models + +import ( + "database/sql/driver" + "encoding/json" + "errors" + "regexp" + "strings" + + "gemma.intevation.de/gemma/pkg/auth" +) + +type ( + Email string + Country string + Role string + UserName string + + BoundingBox struct { + X1 float64 `json:"x1"` + Y1 float64 `json:"y1"` + X2 float64 `json:"x2"` + Y2 float64 `json:"y2"` + } + + User struct { + User UserName `json:"user"` + Role Role `json:"role"` + Password string `json:"password,omitempty"` + Email Email `json:"email"` + Country Country `json:"country"` + Extent *BoundingBox `json:"extent"` + } + + PWResetUser struct { + User string `json:"user"` + } +) + +var ( + // https://stackoverflow.com/questions/201323/how-to-validate-an-email-address-using-a-regular-expression + emailRe = regexp.MustCompile( + `(?:[a-z0-9!#$%&'*+/=?^_` + "`" + + `{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_` + "`" + + `{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]` + + `|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")` + + `@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?` + + `|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}` + + `(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]` + + `:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]` + + `|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])`) + + errNoEmailAddress = errors.New("Not a valid email address") + errNoString = errors.New("Not a string") +) + +func (e *Email) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if !emailRe.MatchString(s) { + return errNoEmailAddress + } + *e = Email(s) + return nil +} + +func (e Email) Value() (driver.Value, error) { + return string(e), nil +} + +func (e *Email) Scan(src interface{}) (err error) { + if s, ok := src.(string); ok { + *e = Email(s) + } else { + err = errNoString + } + return +} + +var errNoValidUser = errors.New("Not a valid user") + +func (u UserName) IsValid() bool { + return u != "" && + !strings.ContainsAny(string(u), auth.InvalidRoleCharacters) +} + +func (u *UserName) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if !emailRe.MatchString(s) { + return errNoEmailAddress + } + user := UserName(s) + if !user.IsValid() { + return errNoValidUser + } + *u = user + return nil +} + +func (u *UserName) Scan(src interface{}) (err error) { + if s, ok := src.(string); ok { + *u = UserName(s) + } else { + err = errNoString + } + return +} + +var ( + validCountries = []string{ + "AT", "BG", "DE", "HU", "HR", + "MD", "RO", "RS", "SK", "UA", + } + errNoValidCountry = errors.New("Not a valid country") +) + +func (c *Country) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + s = strings.ToUpper(s) + for _, v := range validCountries { + if v == s { + *c = Country(v) + return nil + } + } + return errNoValidCountry +} + +func (c Country) Value() (driver.Value, error) { + return string(c), nil +} + +func (c *Country) Scan(src interface{}) (err error) { + if s, ok := src.(string); ok { + *c = Country(s) + } else { + err = errNoString + } + return +} + +var ( + validRoles = []string{ + "waterway_user", + "waterway_admin", + "sys_admin", + } + errNoValidRole = errors.New("Not a valid role") +) + +func (r Role) Value() (driver.Value, error) { + return string(r), nil +} + +func (r *Role) Scan(src interface{}) (err error) { + if s, ok := src.(string); ok { + *r = Role(s) + } else { + err = errNoString + } + return +} + +func (r *Role) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + s = strings.ToLower(s) + for _, v := range validRoles { + if v == s { + *r = Role(v) + return nil + } + } + return errNoValidRole +}
--- a/schema/gemma.sql Wed Aug 22 10:04:04 2018 +0200 +++ b/schema/gemma.sql Wed Aug 22 10:04:52 2018 +0200 @@ -55,8 +55,24 @@ config_key varchar PRIMARY KEY, config_val varchar ) + + CREATE TABLE sys_admin.external_services ( + local_name varchar PRIMARY KEY, + remote_url varchar NOT NULL, + is_wfs boolean NOT NULL DEFAULT TRUE + ) + + CREATE TABLE sys_admin.published_services ( + name varchar PRIMARY KEY, + style bytea, + as_wms boolean NOT NULL DEFAULT TRUE, + as_wfs boolean NOT NULL DEFAULT TRUE + ) ; +-- Tables with geo data to be published with GeoServer. +INSERT INTO sys_admin.published_services (name) VALUES ('fairway_dimensions'); + -- -- Look-up tables with data that are static in a running system --
--- a/schema/install-db.sh Wed Aug 22 10:04:04 2018 +0200 +++ b/schema/install-db.sh Wed Aug 22 10:04:52 2018 +0200 @@ -128,12 +128,9 @@ read a if [[ $a == "yes" ]] ; then dropdb -p "$port" "$db" - for r in `psql -p $port -t -c '\du' | awk -F '|' \ - '$1 "." $3 ~ /waterway_user|waterway_admin|sys_admin|pw_reset/ \ - {print $1}'` - do - dropuser -p "$port" "$r" - done + psql -p $port -A -t -c '\du' | awk -F '|' -v port=$port \ + '$1 "." $3 ~ /waterway_user|waterway_admin|sys_admin|pw_reset/ \ + { system("dropuser -p " port " \"" $1 "\"") }' else echo "No harm done." fi