view cmd/tokenserver/user.go @ 196:b67208d82543

Make test output more comprehensive Running all tests in one transaction ensures the final output tells about any failing test, not just in the last transaction (i.e. test script). The price is that no traces of the tests are left in the database because we have to rollback in order to have no left-over test roles in the cluster.
author Tom Gottfried <tom@intevation.de>
date Fri, 20 Jul 2018 18:31:45 +0200
parents 96bb671cdd98
children
line wrap: on
line source

package main

import (
	"database/sql"
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"regexp"
	"strings"

	"gemma.intevation.de/gemma/auth"
	"github.com/jackc/pgx"
)

type (
	Email   string
	Country string
	Role    string

	BoundingBox struct {
		X1 float64 `json:"x1"`
		Y1 float64 `json:"y1"`
		X2 float64 `json:"x2"`
		Y2 float64 `json:"y2"`
	}

	User struct {
		User     string       `json:"user"`
		Role     Role         `json:"role"`
		Password string       `json:"password"`
		Email    Email        `json:"email"`
		Country  Country      `json:"country"`
		Extent   *BoundingBox `json:"extent"`
	}
)

const (
	createUserSQL       = `SELECT create_user($1, $2, $3, $4, NULL, $5)`
	createUserExtentSQL = `SELECT create_user($1, $2, $3, $4,
  ST_MakeBox2D(ST_Point($5, $6), ST_Point($7, $8)), $9)`
)

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")
)

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
}

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
}

var (
	validRoles = []string{
		"waterway_user",
		"waterway_admin",
		"sys_admin",
	}
	errNoValidRole = errors.New("Not a valid role")
)

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
}

func createUser(rw http.ResponseWriter, req *http.Request) {

	var user User

	defer req.Body.Close()
	if err := json.NewDecoder(req.Body).Decode(&user); err != nil {
		http.Error(rw, "error: "+err.Error(), http.StatusBadRequest)
		return
	}

	token, _ := auth.GetToken(req)
	err := auth.ConnPool.Do(token, func(db *sql.DB) (err error) {
		if user.Extent == nil {
			_, err = db.Exec(
				createUserSQL,
				string(user.Role),
				user.User,
				user.Password,
				string(user.Country),
				string(user.Email),
			)
		} else {
			_, err = db.Exec(
				createUserSQL,
				string(user.Role),
				user.User,
				user.Password,
				string(user.Country),
				user.Extent.X1, user.Extent.Y1,
				user.Extent.X2, user.Extent.Y2,
				string(user.Email),
			)
		}
		return
	})

	var res struct {
		Result  string `json:"result"`
		Code    string `json:"code,omitempty"`
		Message string `json:"message,omitempty"`
	}

	if err != nil {
		if pgErr, ok := err.(pgx.PgError); ok {
			res.Result = "failure"
			res.Code = pgErr.Code
			res.Message = pgErr.Message
		} else {
			log.Printf("err: %v\n", err)
			http.Error(rw,
				"error: "+err.Error(),
				http.StatusInternalServerError)
			return
		}
	} else {
		res.Result = "success"
	}

	rw.Header().Set("Content-Type", "application/json")
	if err := json.NewEncoder(rw).Encode(&res); err != nil {
		log.Printf("error: %v\n", err)
	}
}