Jan Dittberner
335ce16547
- drop migration 2022052601_drop_unused_decisions_colums because it was implicitly part of an earlier migration - add /health endpoint for database health check - add tests for the health check endpoint - add tests for middleware secureHeaders, logRequest and tryAuthenticate - add models.UserModel.CreateUser method
195 lines
4.3 KiB
Go
195 lines
4.3 KiB
Go
/*
|
|
Copyright 2017-2022 CAcert Inc.
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package models
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
const (
|
|
RoleAdmin string = "ADMIN"
|
|
RoleSecretary string = "SECRETARY"
|
|
RoleVoter string = "VOTER"
|
|
)
|
|
|
|
type User struct {
|
|
ID int64 `db:"id"`
|
|
Name string
|
|
Reminder string // reminder email address
|
|
roles []*Role `db:"-"`
|
|
}
|
|
|
|
func (v *User) Roles() ([]*Role, error) {
|
|
if v.roles != nil {
|
|
return v.roles, nil
|
|
}
|
|
|
|
return nil, errors.New("call to GetRoles required")
|
|
}
|
|
|
|
func (v *User) HasRole(roles []string) (bool, error) {
|
|
userRoles, err := v.Roles()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
roleMatched := false
|
|
|
|
outer:
|
|
for _, role := range userRoles {
|
|
for _, checkRole := range roles {
|
|
if role.Name == checkRole {
|
|
roleMatched = true
|
|
|
|
break outer
|
|
}
|
|
}
|
|
}
|
|
|
|
return roleMatched, nil
|
|
}
|
|
|
|
type UserModel struct {
|
|
DB *sqlx.DB
|
|
}
|
|
|
|
func (m *UserModel) GetReminderVoters(_ context.Context) ([]*User, error) {
|
|
panic("not implemented")
|
|
}
|
|
|
|
func (m *UserModel) GetUser(ctx context.Context, emails []string) (*User, error) {
|
|
query, args, err := sqlx.In(
|
|
`SELECT DISTINCT v.id, v.name, v.reminder
|
|
FROM voters v
|
|
JOIN emails e ON e.voter = v.id
|
|
WHERE e.address IN (?)`, emails)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not build query: %w", err)
|
|
}
|
|
|
|
rows, err := m.DB.QueryxContext(ctx, query, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not run query: %w", err)
|
|
}
|
|
|
|
defer func(rows *sqlx.Rows) {
|
|
_ = rows.Close()
|
|
}(rows)
|
|
|
|
var (
|
|
user User
|
|
count int
|
|
)
|
|
|
|
for rows.Next() {
|
|
count++
|
|
|
|
if count > 1 {
|
|
return nil, fmt.Errorf(
|
|
"multiple voters found for addresses in certificate %s",
|
|
strings.Join(emails, ", "),
|
|
)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("could not fetch row: %w", err)
|
|
}
|
|
|
|
if err := rows.StructScan(&user); err != nil {
|
|
return nil, fmt.Errorf("could not get user from row: %w", err)
|
|
}
|
|
}
|
|
|
|
if user.roles, err = m.GetRoles(ctx, &user); err != nil {
|
|
return nil, fmt.Errorf("could not retrieve roles for user %s: %w", user.Name, err)
|
|
}
|
|
|
|
return &user, nil
|
|
}
|
|
|
|
type Role struct {
|
|
Name string `db:"role"`
|
|
}
|
|
|
|
func (m *UserModel) GetRoles(ctx context.Context, user *User) ([]*Role, error) {
|
|
rows, err := m.DB.QueryxContext(ctx, `SELECT role FROM user_roles WHERE voter_id=?`, user.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not query roles for %s: %w", user.Name, err)
|
|
}
|
|
|
|
defer func(rows *sqlx.Rows) {
|
|
_ = rows.Close()
|
|
}(rows)
|
|
|
|
result := make([]*Role, 0)
|
|
|
|
for rows.Next() {
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("could not retrieve row: %w", err)
|
|
}
|
|
|
|
var role Role
|
|
|
|
if err := rows.StructScan(&role); err != nil {
|
|
return nil, fmt.Errorf("could not get role from row: %w", err)
|
|
}
|
|
|
|
result = append(result, &role)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (m *UserModel) CreateUser(ctx context.Context, name string, reminder string, emails []string) (int64, error) {
|
|
tx, err := m.DB.BeginTxx(ctx, nil)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("could not start transaction: %w", err)
|
|
}
|
|
|
|
defer func(tx *sqlx.Tx) {
|
|
_ = tx.Rollback()
|
|
}(tx)
|
|
|
|
res, err := tx.Exec(`INSERT INTO voters (name, reminder, enabled) VALUES (?, ?, 0)`, name, reminder)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("could not insert user: %w", err)
|
|
}
|
|
|
|
userID, err := res.LastInsertId()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("could not get user id: %w", err)
|
|
}
|
|
|
|
for i := range emails {
|
|
_, err := tx.Exec(`INSERT INTO emails (voter, address) VALUES (?, ?)`, userID, emails[i])
|
|
if err != nil {
|
|
return 0, fmt.Errorf("could not insert email %s for voter %s: %w", emails[i], name, err)
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
return 0, fmt.Errorf("could not commit user transaction: %w", err)
|
|
}
|
|
|
|
return userID, nil
|
|
}
|