Moved v3 code from NginxProxyManager/nginx-proxy-manager-3 to NginxProxyManager/nginx-proxy-manager
This commit is contained in:
40
backend/internal/entity/user/capabilities.go
Normal file
40
backend/internal/entity/user/capabilities.go
Normal file
@ -0,0 +1,40 @@
|
||||
package user
|
||||
|
||||
const (
|
||||
// CapabilityFullAdmin can do anything
|
||||
CapabilityFullAdmin = "full-admin"
|
||||
// CapabilityAccessListsView access lists view
|
||||
CapabilityAccessListsView = "access-lists.view"
|
||||
// CapabilityAccessListsManage access lists manage
|
||||
CapabilityAccessListsManage = "access-lists.manage"
|
||||
// CapabilityAuditLogView audit log view
|
||||
CapabilityAuditLogView = "audit-log.view"
|
||||
// CapabilityCertificatesView certificates view
|
||||
CapabilityCertificatesView = "certificates.view"
|
||||
// CapabilityCertificatesManage certificates manage
|
||||
CapabilityCertificatesManage = "certificates.manage"
|
||||
// CapabilityCertificateAuthoritiesView certificate authorities view
|
||||
CapabilityCertificateAuthoritiesView = "certificate-authorities.view"
|
||||
// CapabilityCertificateAuthoritiesManage certificate authorities manage
|
||||
CapabilityCertificateAuthoritiesManage = "certificate-authorities.manage"
|
||||
// CapabilityDNSProvidersView dns providers view
|
||||
CapabilityDNSProvidersView = "dns-providers.view"
|
||||
// CapabilityDNSProvidersManage dns providers manage
|
||||
CapabilityDNSProvidersManage = "dns-providers.manage"
|
||||
// CapabilityHostsView hosts view
|
||||
CapabilityHostsView = "hosts.view"
|
||||
// CapabilityHostsManage hosts manage
|
||||
CapabilityHostsManage = "hosts.manage"
|
||||
// CapabilityHostTemplatesView host-templates view
|
||||
CapabilityHostTemplatesView = "host-templates.view"
|
||||
// CapabilityHostTemplatesManage host-templates manage
|
||||
CapabilityHostTemplatesManage = "host-templates.manage"
|
||||
// CapabilitySettingsManage settings manage
|
||||
CapabilitySettingsManage = "settings.manage"
|
||||
// CapabilityStreamsView streams view
|
||||
CapabilityStreamsView = "streams.view"
|
||||
// CapabilityStreamsManage streams manage
|
||||
CapabilityStreamsManage = "streams.manage"
|
||||
// CapabilityUsersManage users manage
|
||||
CapabilityUsersManage = "users.manage"
|
||||
)
|
25
backend/internal/entity/user/filters.go
Normal file
25
backend/internal/entity/user/filters.go
Normal file
@ -0,0 +1,25 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"npm/internal/entity"
|
||||
)
|
||||
|
||||
var filterMapFunctions = make(map[string]entity.FilterMapFunction)
|
||||
|
||||
// getFilterMapFunctions is a map of functions that should be executed
|
||||
// during the filtering process, if a field is defined here then the value in
|
||||
// the filter will be given to the defined function and it will return a new
|
||||
// value for use in the sql query.
|
||||
func getFilterMapFunctions() map[string]entity.FilterMapFunction {
|
||||
// if len(filterMapFunctions) == 0 {
|
||||
// TODO: See internal/model/file_item.go:620 for an example
|
||||
// }
|
||||
|
||||
return filterMapFunctions
|
||||
}
|
||||
|
||||
// GetFilterSchema returns filter schema
|
||||
func GetFilterSchema() string {
|
||||
var m Model
|
||||
return entity.GetFilterSchema(m)
|
||||
}
|
229
backend/internal/entity/user/methods.go
Normal file
229
backend/internal/entity/user/methods.go
Normal file
@ -0,0 +1,229 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// GetByID finds a user by ID
|
||||
func GetByID(id int) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByID(id)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// GetByEmail finds a user by email
|
||||
func GetByEmail(email string) (Model, error) {
|
||||
var m Model
|
||||
err := m.LoadByEmail(email)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Create will create a User from given model
|
||||
func Create(user *Model) (int, error) {
|
||||
// We need to ensure that a user can't be created with the same email
|
||||
// as an existing non-deleted user. Usually you would do this with the
|
||||
// database schema, but it's a bit more complex because of the is_deleted field.
|
||||
|
||||
if user.ID != 0 {
|
||||
return 0, goerrors.New("Cannot create user when model already has an ID")
|
||||
}
|
||||
|
||||
// Check if an existing user with this email exists
|
||||
_, err := GetByEmail(user.Email)
|
||||
if err == nil {
|
||||
return 0, errors.ErrDuplicateEmailUser
|
||||
}
|
||||
|
||||
user.Touch(true)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
result, err := db.NamedExec(`INSERT INTO `+fmt.Sprintf("`%s`", tableName)+` (
|
||||
created_on,
|
||||
modified_on,
|
||||
name,
|
||||
nickname,
|
||||
email,
|
||||
is_disabled
|
||||
) VALUES (
|
||||
:created_on,
|
||||
:modified_on,
|
||||
:name,
|
||||
:nickname,
|
||||
:email,
|
||||
:is_disabled
|
||||
)`, user)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
last, lastErr := result.LastInsertId()
|
||||
if lastErr != nil {
|
||||
return 0, lastErr
|
||||
}
|
||||
|
||||
return int(last), nil
|
||||
}
|
||||
|
||||
// Update will Update a User from this model
|
||||
func Update(user *Model) error {
|
||||
if user.ID == 0 {
|
||||
return goerrors.New("Cannot update user when model doesn't have an ID")
|
||||
}
|
||||
|
||||
// Check that the email address isn't associated with another user
|
||||
if existingUser, _ := GetByEmail(user.Email); existingUser.ID != 0 && existingUser.ID != user.ID {
|
||||
return errors.ErrDuplicateEmailUser
|
||||
}
|
||||
|
||||
user.Touch(false)
|
||||
|
||||
db := database.GetInstance()
|
||||
// nolint: gosec
|
||||
_, err := db.NamedExec(`UPDATE `+fmt.Sprintf("`%s`", tableName)+` SET
|
||||
created_on = :created_on,
|
||||
modified_on = :modified_on,
|
||||
name = :name,
|
||||
nickname = :nickname,
|
||||
email = :email,
|
||||
is_disabled = :is_disabled,
|
||||
is_deleted = :is_deleted
|
||||
WHERE id = :id`, user)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// IsEnabled is used by middleware to ensure the user is still enabled
|
||||
// returns (userExist, isEnabled)
|
||||
func IsEnabled(userID int) (bool, bool) {
|
||||
// nolint: gosec
|
||||
query := `SELECT is_disabled FROM ` + fmt.Sprintf("`%s`", tableName) + ` WHERE id = ? AND is_deleted = ?`
|
||||
disabled := true
|
||||
db := database.GetInstance()
|
||||
err := db.QueryRowx(query, userID, 0).Scan(&disabled)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return false, false
|
||||
} else if err != nil {
|
||||
logger.Error("QueryError", err)
|
||||
}
|
||||
|
||||
return true, !disabled
|
||||
}
|
||||
|
||||
// List will return a list of users
|
||||
func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) {
|
||||
var result ListResponse
|
||||
var exampleModel Model
|
||||
|
||||
defaultSort := model.Sort{
|
||||
Field: "name",
|
||||
Direction: "ASC",
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
if db == nil {
|
||||
return result, errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
/*
|
||||
filters = append(filters, model.Filter{
|
||||
Field: "is_system",
|
||||
Modifier: "equals",
|
||||
Value: []string{"0"},
|
||||
})
|
||||
*/
|
||||
|
||||
// Get count of items in this search
|
||||
query, params := entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), true)
|
||||
countRow := db.QueryRowx(query, params...)
|
||||
var totalRows int
|
||||
queryErr := countRow.Scan(&totalRows)
|
||||
if queryErr != nil && queryErr != sql.ErrNoRows {
|
||||
logger.Debug("Query: %s -- %+v", query, params)
|
||||
return result, queryErr
|
||||
}
|
||||
|
||||
// Get rows
|
||||
var items []Model
|
||||
query, params = entity.ListQueryBuilder(exampleModel, tableName, &pageInfo, defaultSort, filters, getFilterMapFunctions(), false)
|
||||
err := db.Select(&items, query, params...)
|
||||
if err != nil {
|
||||
logger.Debug("Query: %s -- %+v", query, params)
|
||||
return result, err
|
||||
}
|
||||
|
||||
for idx := range items {
|
||||
items[idx].generateGravatar()
|
||||
}
|
||||
|
||||
if expand != nil {
|
||||
for idx := range items {
|
||||
expandErr := items[idx].Expand(expand)
|
||||
if expandErr != nil {
|
||||
logger.Error("UsersExpansionError", expandErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = ListResponse{
|
||||
Items: items,
|
||||
Total: totalRows,
|
||||
Limit: pageInfo.Limit,
|
||||
Offset: pageInfo.Offset,
|
||||
Sort: pageInfo.Sort,
|
||||
Filter: filters,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DeleteAll will do just that, and should only be used for testing purposes.
|
||||
func DeleteAll() error {
|
||||
db := database.GetInstance()
|
||||
_, err := db.Exec(fmt.Sprintf("DELETE FROM `%s`", tableName))
|
||||
return err
|
||||
}
|
||||
|
||||
// GetCapabilities gets capabilities for a user
|
||||
func GetCapabilities(userID int) ([]string, error) {
|
||||
var capabilities []string
|
||||
db := database.GetInstance()
|
||||
if db == nil {
|
||||
return []string{}, errors.ErrDatabaseUnavailable
|
||||
}
|
||||
|
||||
query := `SELECT c.name FROM "user_has_capability" h
|
||||
INNER JOIN "capability" c ON c.id = h.capability_id
|
||||
WHERE h.user_id = ?`
|
||||
|
||||
rows, err := db.Query(query, userID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
logger.Debug("QUERY: %v -- %v", query, userID)
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
// nolint: errcheck
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var name string
|
||||
err := rows.Scan(&name)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
capabilities = append(capabilities, name)
|
||||
}
|
||||
|
||||
return capabilities, nil
|
||||
}
|
191
backend/internal/entity/user/model.go
Normal file
191
backend/internal/entity/user/model.go
Normal file
@ -0,0 +1,191 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"npm/internal/database"
|
||||
"npm/internal/entity/auth"
|
||||
"npm/internal/errors"
|
||||
"npm/internal/logger"
|
||||
"npm/internal/types"
|
||||
"npm/internal/util"
|
||||
|
||||
"github.com/drexedam/gravatar"
|
||||
)
|
||||
|
||||
const (
|
||||
tableName = "user"
|
||||
)
|
||||
|
||||
// Model is the user model
|
||||
type Model struct {
|
||||
ID int `json:"id" db:"id" filter:"id,integer"`
|
||||
Name string `json:"name" db:"name" filter:"name,string"`
|
||||
Nickname string `json:"nickname" db:"nickname" filter:"nickname,string"`
|
||||
Email string `json:"email" db:"email" filter:"email,email"`
|
||||
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"`
|
||||
ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"`
|
||||
GravatarURL string `json:"gravatar_url"`
|
||||
IsDisabled bool `json:"is_disabled" db:"is_disabled" filter:"is_disabled,boolean"`
|
||||
IsSystem bool `json:"is_system,omitempty" db:"is_system"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
|
||||
// Expansions
|
||||
Auth *auth.Model `json:"auth,omitempty" db:"-"`
|
||||
Capabilities []string `json:"capabilities,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Model) getByQuery(query string, params []interface{}) error {
|
||||
err := database.GetByQuery(m, query, params)
|
||||
m.generateGravatar()
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadByID will load from an ID
|
||||
func (m *Model) LoadByID(id int) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ? AND is_deleted = ? LIMIT 1", tableName)
|
||||
params := []interface{}{id, false}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// LoadByEmail will load from an Email
|
||||
func (m *Model) LoadByEmail(email string) error {
|
||||
query := fmt.Sprintf("SELECT * FROM `%s` WHERE email = ? AND is_deleted = ? AND is_system = ? LIMIT 1", tableName)
|
||||
params := []interface{}{strings.TrimSpace(strings.ToLower(email)), false, false}
|
||||
return m.getByQuery(query, params)
|
||||
}
|
||||
|
||||
// Touch will update model's timestamp(s)
|
||||
func (m *Model) Touch(created bool) {
|
||||
var d types.DBDate
|
||||
d.Time = time.Now()
|
||||
if created {
|
||||
m.CreatedOn = d
|
||||
}
|
||||
m.ModifiedOn = d
|
||||
m.generateGravatar()
|
||||
}
|
||||
|
||||
// Save will save this model to the DB
|
||||
func (m *Model) Save() error {
|
||||
var err error
|
||||
// Ensure email is nice
|
||||
m.Email = strings.TrimSpace(strings.ToLower(m.Email))
|
||||
|
||||
if m.IsSystem {
|
||||
return errors.ErrSystemUserReadonly
|
||||
}
|
||||
|
||||
if m.ID == 0 {
|
||||
m.ID, err = Create(m)
|
||||
} else {
|
||||
err = Update(m)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete will mark a user as deleted
|
||||
func (m *Model) Delete() bool {
|
||||
m.Touch(false)
|
||||
m.IsDeleted = true
|
||||
if err := m.Save(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SetPermissions will wipe out any existing permissions and add new ones for this user
|
||||
func (m *Model) SetPermissions(permissions []string) error {
|
||||
if m.ID == 0 {
|
||||
return fmt.Errorf("Cannot set permissions without first saving the User")
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
|
||||
// Wipe out previous permissions
|
||||
query := `DELETE FROM "user_has_capability" WHERE "user_id" = ?`
|
||||
if _, err := db.Exec(query, m.ID); err != nil {
|
||||
logger.Debug("QUERY: %v -- %v", query, m.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(permissions) > 0 {
|
||||
// Add new permissions
|
||||
for _, permission := range permissions {
|
||||
query = `INSERT INTO "user_has_capability" (
|
||||
"user_id", "capability_id"
|
||||
) VALUES (
|
||||
?,
|
||||
(SELECT id FROM capability WHERE name = ?)
|
||||
)`
|
||||
|
||||
_, err := db.Exec(query, m.ID, permission)
|
||||
if err != nil {
|
||||
logger.Debug("QUERY: %v -- %v -- %v", query, m.ID, permission)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Expand will fill in more properties
|
||||
func (m *Model) Expand(items []string) error {
|
||||
var err error
|
||||
|
||||
if util.SliceContainsItem(items, "capabilities") && m.ID > 0 {
|
||||
m.Capabilities, err = GetCapabilities(m.ID)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Model) generateGravatar() {
|
||||
m.GravatarURL = gravatar.New(m.Email).
|
||||
Size(128).
|
||||
Default(gravatar.MysteryMan).
|
||||
Rating(gravatar.Pg).
|
||||
AvatarURL()
|
||||
}
|
||||
|
||||
// SaveCapabilities will save the capabilities of the user.
|
||||
func (m *Model) SaveCapabilities() error {
|
||||
// m.Capabilities
|
||||
if m.ID == 0 {
|
||||
return fmt.Errorf("Cannot save capabilities on unsaved user")
|
||||
}
|
||||
|
||||
// there must be at least 1 capability
|
||||
if len(m.Capabilities) == 0 {
|
||||
return goerrors.New("At least 1 capability required for a user")
|
||||
}
|
||||
|
||||
db := database.GetInstance()
|
||||
|
||||
// Get a full list of capabilities
|
||||
var capabilities []string
|
||||
query := `SELECT "name" from "capability"`
|
||||
err := db.Select(&capabilities, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check that the capabilities defined exist in the db
|
||||
for _, cap := range m.Capabilities {
|
||||
found := false
|
||||
for _, a := range capabilities {
|
||||
if a == cap {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("Capability `%s` is not valid", cap)
|
||||
}
|
||||
}
|
||||
|
||||
return m.SetPermissions(m.Capabilities)
|
||||
}
|
15
backend/internal/entity/user/structs.go
Normal file
15
backend/internal/entity/user/structs.go
Normal file
@ -0,0 +1,15 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"npm/internal/model"
|
||||
)
|
||||
|
||||
// ListResponse is the JSON response for users list
|
||||
type ListResponse struct {
|
||||
Total int `json:"total"`
|
||||
Offset int `json:"offset"`
|
||||
Limit int `json:"limit"`
|
||||
Sort []model.Sort `json:"sort"`
|
||||
Filter []model.Filter `json:"filter,omitempty"`
|
||||
Items []Model `json:"items,omitempty"`
|
||||
}
|
Reference in New Issue
Block a user