Auth: Refactor user management API and CLI commands #98

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2023-03-09 15:12:10 +01:00
parent 22f8535ad9
commit d8ab9616a5
20 changed files with 304 additions and 137 deletions

View file

@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: ci@photoprism.app\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-09 12:51+0000\n"
"PO-Revision-Date: 2023-02-26 21:43+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-09 12:51+0000\n"
"POT-Creation-Date: 2023-03-09 13:14+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: ci@photoprism.app\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-02-09 12:51+0000\n"
"PO-Revision-Date: 2023-02-26 09:43+0000\n"
"Last-Translator: Lulu195 <bimasakti.ro@gmail.com>\n"

View file

@ -33,6 +33,7 @@ import { config } from "app/session";
export class User extends RestModel {
getDefaults() {
return {
ID: 0,
UID: "",
UUID: "",
AuthProvider: "",
@ -185,8 +186,8 @@ export class User extends RestModel {
);
}
isLocal() {
return !this.AuthProvider || this.AuthProvider === "local";
isRemote() {
return this.AuthProvider && this.AuthProvider === "ldap";
}
changePassword(oldPassword, newPassword) {

View file

@ -2,6 +2,8 @@ package acl
import (
"strings"
"github.com/photoprism/photoprism/pkg/txt"
)
// Role represents a user role.
@ -12,6 +14,15 @@ func (r Role) String() string {
return string(r)
}
// Pretty returns the type in an easy-to-read format.
func (r Role) Pretty() string {
if r == RoleUnknown {
return "Unknown"
}
return txt.UpperFirst(string(r))
}
// LogId returns an identifier string for use in log messages.
func (r Role) LogId() string {
return "role " + r.String()

View file

@ -8,7 +8,6 @@ import (
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/report"
"github.com/photoprism/photoprism/pkg/txt"
)
@ -24,7 +23,7 @@ var UsersListCommand = cli.Command{
// usersListAction displays existing user accounts.
func usersListAction(ctx *cli.Context) error {
return CallWithDependencies(ctx, func(conf *config.Config) error {
cols := []string{"UID", "Username", "Role", "Auth Provider", "Super Admin", "Web Login", "WebDAV", "Created At"}
cols := []string{"UID", "Username", "Role", "Authentication", "Super Admin", "Web Login", "Last Login", "WebDAV"}
// Fetch users from database.
users := query.RegisteredUsers()
@ -38,12 +37,12 @@ func usersListAction(ctx *cli.Context) error {
rows[i] = []string{
user.UID(),
user.Username(),
user.AclRole().String(),
authn.ProviderString(user.Provider()),
user.AclRole().Pretty(),
user.Provider().Pretty(),
report.Bool(user.SuperAdmin, report.Yes, report.No),
report.Bool(user.CanLogIn(), report.Enabled, report.Disabled),
txt.TimeStamp(user.LoginAt),
report.Bool(user.CanUseWebDAV(), report.Enabled, report.Disabled),
txt.TimeStamp(&user.CreatedAt),
}
}

View file

@ -12,6 +12,7 @@ import (
"github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/txt"
@ -263,17 +264,17 @@ func (m *Session) Username() string {
}
// Provider returns the authentication provider name.
func (m *Session) Provider() string {
return m.AuthProvider
func (m *Session) Provider() authn.ProviderType {
return authn.Provider(m.AuthProvider)
}
// SetProvider updates the session's authentication provider.
func (m *Session) SetProvider(provider string) *Session {
func (m *Session) SetProvider(provider authn.ProviderType) *Session {
if provider == "" {
return m
}
m.AuthProvider = provider
m.AuthProvider = provider.String()
return m
}

View file

@ -16,7 +16,7 @@ import (
)
// Auth checks if the credentials are valid and returns the user and authentication provider.
var Auth = func(f form.Login, m *Session, c *gin.Context) (user *User, provider string, err error) {
var Auth = func(f form.Login, m *Session, c *gin.Context) (user *User, provider authn.ProviderType, err error) {
name := f.Username()
user = FindUserByName(name)
@ -84,7 +84,7 @@ func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
}
var user *User
var provider string
var provider authn.ProviderType
// Login credentials provided?
if f.HasCredentials() {

View file

@ -39,7 +39,7 @@ type Users []User
// User represents a person that may optionally log in as user.
type User struct {
ID int `gorm:"primary_key" json:"-" yaml:"-"`
ID int `gorm:"primary_key" json:"ID" yaml:"-"`
UUID string `gorm:"type:VARBINARY(64);column:user_uuid;index;" json:"UUID,omitempty" yaml:"UUID,omitempty"`
UserUID string `gorm:"type:VARBINARY(42);column:user_uid;unique_index;" json:"UID" yaml:"UID"`
AuthProvider string `gorm:"type:VARBINARY(128);default:'';" json:"AuthProvider" yaml:"AuthProvider,omitempty"`
@ -83,7 +83,7 @@ func (User) TableName() string {
return "auth_users"
}
// NewUser creates a new user and returns it.
// NewUser creates a new user entity with defaults.
func NewUser() (m *User) {
uid := rnd.GenerateUID(UserUID)
@ -97,6 +97,15 @@ func NewUser() (m *User) {
}
}
// LdapUser creates an LDAP user entity.
func LdapUser(username, dn string) User {
return User{
UserName: clean.Username(username),
AuthID: dn,
AuthProvider: authn.ProviderLDAP.String(),
}
}
// FindUser returns the matching user or nil if it was not found.
func FindUser(find User) *User {
m := &User{}
@ -107,8 +116,6 @@ func FindUser(find User) *User {
stmt = stmt.Where("id = ?", find.ID)
} else if rnd.IsUID(find.UserUID, UserUID) {
stmt = stmt.Where("user_uid = ?", find.UserUID)
} else if find.UserName != "" && find.AuthProvider != "" {
stmt = stmt.Where("user_name = ? AND (auth_provider = ? OR auth_provider = '')", find.UserName, find.AuthProvider)
} else if find.UserName != "" {
stmt = stmt.Where("user_name = ?", find.UserName)
} else if find.UserEmail != "" {
@ -157,13 +164,24 @@ func FindUserByName(userName string) *User {
// FindLocalUser returns the matching local user or nil if it was not found.
func FindLocalUser(userName string) *User {
userName = clean.Username(userName)
name := clean.Username(userName)
if userName == "" {
if name == "" {
return nil
}
return FindUser(User{UserName: userName, AuthProvider: authn.ProviderLocal})
m := &User{}
providers := authn.LocalProviders
// Build query.
if err := UnscopedDb().
Where("user_name = ? AND auth_provider IN (?)", name, providers).
First(m).Error; err != nil {
return nil
}
// Return with related records.
return m.LoadRelated()
}
// FindUserByUID returns the matching user or nil if it was not found.
@ -459,9 +477,9 @@ func (m *User) String() string {
}
// Provider returns the authentication provider name.
func (m *User) Provider() string {
func (m *User) Provider() authn.ProviderType {
if m.AuthProvider != "" {
return m.AuthProvider
return authn.ProviderType(m.AuthProvider)
} else if m.ID == Visitor.ID {
return authn.ProviderToken
} else if m.ID == 1 {
@ -474,29 +492,18 @@ func (m *User) Provider() string {
}
// SetProvider set the authentication provider.
func (m *User) SetProvider(s string) *User {
func (m *User) SetProvider(t authn.ProviderType) *User {
if m == nil {
return nil
} else if m.ID <= 0 {
return m
} else if s == authn.ProviderString(authn.ProviderDefault) {
s = ""
}
m.AuthProvider = clean.TypeLower(s)
m.AuthProvider = t.String()
return m
}
// IsLocal checks if the user is authenticated locally.
func (m *User) IsLocal() bool {
if m.UserName == "" || m.ID <= 0 {
return false
}
return m.ID == 1 || m.AuthProvider == authn.ProviderDefault || m.AuthProvider == authn.ProviderLocal
}
// Username returns the user's login name as sanitized string.
func (m *User) Username() string {
return clean.Username(m.UserName)
@ -524,7 +531,7 @@ func (m *User) SetUsername(login string) (err error) {
// Update display name.
if m.DisplayName == "" || m.DisplayName == AdminDisplayName && m.ID == 1 {
m.DisplayName = clean.NameCapitalized(login)
m.DisplayName = m.FullName()
}
return nil
@ -556,12 +563,15 @@ func (m *User) Handle() string {
// FullName returns the name of the user for display purposes.
func (m *User) FullName() string {
switch {
case m.DisplayName != "":
if m.DisplayName != "" {
return m.DisplayName
default:
return clean.NameCapitalized(strings.ReplaceAll(m.Handle(), ".", " "))
}
if n := m.Details().DisplayName(); n != "" {
return n
}
return clean.NameCapitalized(strings.ReplaceAll(m.Handle(), ".", " "))
}
// AclRole returns the user role for ACL permission checks.
@ -805,7 +815,7 @@ func (m *User) Validate() (err error) {
// SetFormValues sets the values specified in the form.
func (m *User) SetFormValues(frm form.User) *User {
m.UserName = frm.Username()
m.SetProvider(frm.AuthProvider)
m.SetProvider(frm.Provider())
m.UserEmail = frm.Email()
m.DisplayName = frm.DisplayName
m.SuperAdmin = frm.SuperAdmin
@ -816,6 +826,11 @@ func (m *User) SetFormValues(frm form.User) *User {
m.SetBasePath(frm.BasePath)
m.SetUploadPath(frm.UploadPath)
// Set display name default if empty.
if m.DisplayName == "" || m.DisplayName == AdminDisplayName && m.ID == 1 {
m.DisplayName = m.FullName()
}
return m
}
@ -961,6 +976,11 @@ func (m *User) SaveForm(f form.User, updateRights bool) error {
m.SetDisplayName(n, SrcManual)
}
// Set display name default if empty.
if m.DisplayName == "" || m.DisplayName == AdminDisplayName && m.ID == 1 {
m.DisplayName = m.FullName()
}
// Sanitize email address.
if email := clean.Email(f.UserEmail); email != "" && email != m.UserEmail {
m.UserEmail = email
@ -971,16 +991,29 @@ func (m *User) SaveForm(f form.User, updateRights bool) error {
// Update user rights only if explicitly requested.
if updateRights {
m.UserRole = f.UserRole
m.UserAttr = f.UserAttr
m.SuperAdmin = f.SuperAdmin
m.CanLogin = f.CanLogin
m.WebDAV = f.WebDAV
m.UserAttr = f.UserAttr
m.SetProvider(f.AuthProvider)
m.SetProvider(f.Provider())
m.SetBasePath(f.BasePath)
m.SetUploadPath(f.UploadPath)
}
// Ensure super admins never have a non-admin role.
if m.SuperAdmin {
m.UserRole = acl.RoleAdmin.String()
}
// Make sure that the initial admin user cannot lock itself out.
if m.ID == Admin.ID && (m.AclRole() != acl.RoleAdmin || !m.SuperAdmin || !m.CanLogin) {
m.UserRole = acl.RoleAdmin.String()
m.SuperAdmin = true
m.CanLogin = true
}
return m.Save()
}
@ -989,12 +1022,18 @@ func (m *User) SetDisplayName(name, src string) *User {
name = clean.Name(name)
d := m.Details()
priority := SrcPriority[src] >= SrcPriority[d.NameSrc]
if name == "" || SrcPriority[src] < SrcPriority[d.NameSrc] {
if name == "" || !priority && m.DisplayName != "" {
return m
}
m.DisplayName = name
if !priority {
return m
}
d.NameSrc = src
// Try to parse name into components.

View file

@ -18,7 +18,7 @@ const (
var Admin = User{
ID: 1,
UserName: AdminUserName,
AuthProvider: authn.ProviderLocal,
AuthProvider: authn.ProviderLocal.String(),
UserRole: acl.RoleAdmin.String(),
DisplayName: AdminDisplayName,
SuperAdmin: true,
@ -35,7 +35,7 @@ var UnknownUser = User{
ID: -1,
UserUID: "u000000000000001",
UserName: "",
AuthProvider: authn.ProviderNone,
AuthProvider: authn.ProviderNone.String(),
UserRole: acl.RoleUnknown.String(),
CanLogin: false,
WebDAV: false,
@ -51,7 +51,7 @@ var Visitor = User{
ID: -2,
UserUID: "u000000000000002",
UserName: "",
AuthProvider: authn.ProviderToken,
AuthProvider: authn.ProviderToken.String(),
UserRole: acl.RoleVisitor.String(),
DisplayName: VisitorDisplayName,
CanLogin: false,

View file

@ -2,6 +2,7 @@ package entity
import (
"fmt"
"strings"
"time"
"github.com/photoprism/photoprism/pkg/clean"
@ -102,6 +103,37 @@ func (m *UserDetails) Updates(values interface{}) error {
return UnscopedDb().Model(m).Updates(values).Error
}
// DisplayName returns a display name based on the user details.
func (m *UserDetails) DisplayName() string {
if m == nil {
return ""
}
n := make([]string, 0, 3)
// Add title if exists.
if m.NameTitle != "" {
n = append(n, m.NameTitle)
}
// Add given name if exists.
if m.GivenName != "" {
n = append(n, m.GivenName)
}
// Add family name if exists.
if m.FamilyName != "" {
n = append(n, m.FamilyName)
}
// Default to nick name.
if len(n) == 0 {
return m.NickName
}
return strings.Join(n, " ")
}
// SetGivenName updates the user's given name.
func (m *UserDetails) SetGivenName(name string) *UserDetails {
name = clean.Name(name)

View file

@ -2,6 +2,7 @@ package entity
import (
"github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/pkg/authn"
)
type UserMap map[string]User
@ -27,17 +28,18 @@ func (m UserMap) Pointer(name string) *User {
// UserFixtures specifies user fixtures for use in tests.
var UserFixtures = UserMap{
"alice": {
ID: 5,
UserUID: "uqxetse3cy5eo9z2",
UserName: "alice",
DisplayName: "Alice",
UserEmail: "alice@example.com",
UserRole: acl.RoleAdmin.String(),
SuperAdmin: true,
CanLogin: true,
WebDAV: true,
CanInvite: true,
InviteToken: GenerateToken(),
ID: 5,
UserUID: "uqxetse3cy5eo9z2",
UserName: "alice",
DisplayName: "Alice",
UserEmail: "alice@example.com",
UserRole: acl.RoleAdmin.String(),
AuthProvider: authn.ProviderLocal.String(),
SuperAdmin: true,
CanLogin: true,
WebDAV: true,
CanInvite: true,
InviteToken: GenerateToken(),
UserSettings: &UserSettings{
UITheme: "",
MapsStyle: "",
@ -51,16 +53,17 @@ var UserFixtures = UserMap{
},
},
"bob": {
ID: 7,
UserUID: "uqxc08w3d0ej2283",
UserName: "bob",
DisplayName: "Robert Rich",
UserEmail: "bob@example.com",
UserRole: acl.RoleAdmin.String(),
SuperAdmin: false,
CanLogin: true,
WebDAV: true,
CanInvite: false,
ID: 7,
UserUID: "uqxc08w3d0ej2283",
UserName: "bob",
DisplayName: "Robert Rich",
UserEmail: "bob@example.com",
UserRole: acl.RoleAdmin.String(),
AuthProvider: authn.ProviderLocal.String(),
SuperAdmin: false,
CanLogin: true,
WebDAV: true,
CanInvite: false,
UserSettings: &UserSettings{
UITheme: "grayscale",
MapsStyle: "topographique",
@ -77,16 +80,17 @@ var UserFixtures = UserMap{
},
},
"friend": {
ID: 8,
UserUID: "uqxqg7i1kperxvu7",
UserName: "friend",
UserEmail: "friend@example.com",
UserRole: acl.RoleAdmin.String(),
SuperAdmin: false,
DisplayName: "Guy Friend",
CanLogin: true,
WebDAV: false,
CanInvite: false,
ID: 8,
UserUID: "uqxqg7i1kperxvu7",
UserName: "friend",
UserEmail: "friend@example.com",
UserRole: acl.RoleAdmin.String(),
AuthProvider: authn.ProviderLocal.String(),
SuperAdmin: false,
DisplayName: "Guy Friend",
CanLogin: true,
WebDAV: false,
CanInvite: false,
UserSettings: &UserSettings{
UITheme: "gemstone",
MapsStyle: "hybrid",
@ -99,17 +103,18 @@ var UserFixtures = UserMap{
},
},
"deleted": {
ID: 10000008,
UserUID: "uqxqg7i1kperxvu8",
UserName: "deleted",
UserEmail: "",
DisplayName: "Deleted User",
SuperAdmin: false,
UserRole: acl.RoleVisitor.String(),
CanLogin: false,
WebDAV: true,
CanInvite: false,
DeletedAt: TimePointer(),
ID: 10000008,
UserUID: "uqxqg7i1kperxvu8",
UserName: "deleted",
UserEmail: "",
DisplayName: "Deleted User",
SuperAdmin: false,
UserRole: acl.RoleVisitor.String(),
AuthProvider: authn.ProviderLocal.String(),
CanLogin: false,
WebDAV: true,
CanInvite: false,
DeletedAt: TimePointer(),
UserSettings: &UserSettings{
UITheme: "",
MapsStyle: "",
@ -119,16 +124,17 @@ var UserFixtures = UserMap{
},
},
"unauthorized": {
ID: 10000009,
UserUID: "uriku0138hqql4bz",
UserName: "jens.mander",
UserEmail: "jens.mander@microsoft.com",
UserRole: acl.RoleUnknown.String(),
SuperAdmin: false,
DisplayName: "Jens Mander",
CanLogin: true,
WebDAV: true,
CanInvite: false,
ID: 10000009,
UserUID: "uriku0138hqql4bz",
UserName: "jens.mander",
UserEmail: "jens.mander@microsoft.com",
UserRole: acl.RoleUnknown.String(),
AuthProvider: authn.ProviderNone.String(),
SuperAdmin: false,
DisplayName: "Jens Mander",
CanLogin: true,
WebDAV: true,
CanInvite: false,
UserSettings: &UserSettings{
UITheme: "",
MapsStyle: "",
@ -141,17 +147,18 @@ var UserFixtures = UserMap{
},
},
"fowler": {
ID: 10000023,
UserUID: "urinotv3d6jedvlm",
UserName: "fowler",
DisplayName: "Martin Fowler",
UserEmail: "martin@fowler.org",
UserRole: acl.RoleAdmin.String(),
SuperAdmin: false,
CanLogin: true,
WebDAV: true,
CanInvite: true,
InviteToken: GenerateToken(),
ID: 10000023,
UserUID: "urinotv3d6jedvlm",
UserName: "fowler",
DisplayName: "Martin Fowler",
UserEmail: "martin@fowler.org",
UserRole: acl.RoleAdmin.String(),
AuthProvider: authn.ProviderLocal.String(),
SuperAdmin: false,
CanLogin: true,
WebDAV: true,
CanInvite: true,
InviteToken: GenerateToken(),
UserSettings: &UserSettings{
UITheme: "custom",
MapsStyle: "invalid",

View file

@ -3,13 +3,14 @@ package form
import (
"github.com/urfave/cli"
"github.com/photoprism/photoprism/pkg/authn"
"github.com/photoprism/photoprism/pkg/clean"
)
// User represents a user account form.
type User struct {
UserName string `json:"Name,omitempty" yaml:"Name,omitempty"`
AuthProvider string `json:"Provider,omitempty" yaml:"Provider,omitempty"`
AuthProvider string `json:"AuthProvider,omitempty" yaml:"AuthProvider,omitempty"`
UserEmail string `json:"Email,omitempty" yaml:"Email,omitempty"`
DisplayName string `json:"DisplayName,omitempty" yaml:"DisplayName,omitempty"`
UserRole string `json:"Role,omitempty" yaml:"Role,omitempty"`
@ -27,7 +28,7 @@ type User struct {
func NewUserFromCli(ctx *cli.Context) User {
return User{
UserName: clean.Username(ctx.Args().First()),
AuthProvider: clean.TypeLower(ctx.String("provider")),
AuthProvider: clean.TypeLower(ctx.String("auth")),
UserEmail: clean.Email(ctx.String("email")),
DisplayName: clean.Name(ctx.String("name")),
UserRole: clean.Role(ctx.String("role")),
@ -47,8 +48,8 @@ func (f *User) Username() string {
}
// Provider returns the sanitized auth provider name.
func (f *User) Provider() string {
return clean.TypeLower(f.AuthProvider)
func (f *User) Provider() authn.ProviderType {
return authn.Provider(f.AuthProvider)
}
// Email returns the sanitized email in lowercase.

View file

@ -153,4 +153,10 @@ var DialectMySQL = Migrations{
Stage: "main",
Statements: []string{"ALTER TABLE files MODIFY IF EXISTS file_colors VARBINARY(18);", "ALTER TABLE files MODIFY IF EXISTS File_luminance VARBINARY(18);"},
},
{
ID: "20230309-000001",
Dialect: "mysql",
Stage: "main",
Statements: []string{"UPDATE auth_users SET auth_provider = 'local' WHERE id = 1;", "UPDATE auth_users SET auth_provider = 'none' WHERE id = -1;", "UPDATE auth_users SET auth_provider = 'token' WHERE id = -2;", "UPDATE auth_users SET auth_provider = 'default' WHERE auth_provider = '' OR auth_provider = 'password' OR auth_provider IS NULL;"},
},
}

View file

@ -81,4 +81,10 @@ var DialectSQLite3 = Migrations{
Stage: "pre",
Statements: []string{"ALTER TABLE files_sync RENAME COLUMN account_id TO service_id;", "ALTER TABLE files_share RENAME COLUMN account_id TO service_id;"},
},
{
ID: "20230309-000001",
Dialect: "sqlite3",
Stage: "main",
Statements: []string{"UPDATE auth_users SET auth_provider = 'local' WHERE id = 1;", "UPDATE auth_users SET auth_provider = 'none' WHERE id = -1;", "UPDATE auth_users SET auth_provider = 'token' WHERE id = -2;", "UPDATE auth_users SET auth_provider = 'default' WHERE auth_provider = '' OR auth_provider = 'password' OR auth_provider IS NULL;"},
},
}

View file

@ -0,0 +1,4 @@
UPDATE auth_users SET auth_provider = 'local' WHERE id = 1;
UPDATE auth_users SET auth_provider = 'none' WHERE id = -1;
UPDATE auth_users SET auth_provider = 'token' WHERE id = -2;
UPDATE auth_users SET auth_provider = 'default' WHERE auth_provider = '' OR auth_provider = 'password' OR auth_provider IS NULL;

View file

@ -0,0 +1,4 @@
UPDATE auth_users SET auth_provider = 'local' WHERE id = 1;
UPDATE auth_users SET auth_provider = 'none' WHERE id = -1;
UPDATE auth_users SET auth_provider = 'token' WHERE id = -2;
UPDATE auth_users SET auth_provider = 'default' WHERE auth_provider = '' OR auth_provider = 'password' OR auth_provider IS NULL;

View file

@ -1,19 +1,75 @@
package authn
import (
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/list"
"github.com/photoprism/photoprism/pkg/txt"
)
// ProviderType represents an authentication provider type.
type ProviderType string
// Authentication providers.
const (
ProviderDefault = ""
ProviderNone = "none"
ProviderToken = "token"
ProviderLocal = "local"
ProviderLDAP = "ldap"
ProviderDefault ProviderType = "default"
ProviderLocal ProviderType = "local"
ProviderLDAP ProviderType = "ldap"
ProviderToken ProviderType = "token"
ProviderNone ProviderType = "none"
ProviderUnknown ProviderType = ""
)
// ProviderString returns the provider name as a string for use in logs and reports.
func ProviderString(s string) string {
if s == ProviderDefault {
return "default"
// RemoteProviders lists all remote auth providers.
var RemoteProviders = list.List{
string(ProviderLDAP),
}
// LocalProviders lists all local auth providers.
var LocalProviders = list.List{
string(ProviderLocal),
}
// IsRemote checks if the provider is external.
func (t ProviderType) IsRemote() bool {
return list.Contains(RemoteProviders, string(t))
}
// IsLocal checks if local authentication is possible.
func (t ProviderType) IsLocal() bool {
return list.Contains(LocalProviders, string(t))
}
// String returns the provider identifier as a string.
func (t ProviderType) String() string {
if t == ProviderUnknown {
return string(ProviderDefault)
} else if t == "password" {
return string(ProviderLocal)
}
return s
return string(t)
}
// Pretty returns the provider identifier in an easy-to-read format.
func (t ProviderType) Pretty() string {
switch t {
case ProviderLDAP:
return "LDAP/AD"
default:
return txt.UpperFirst(t.String())
}
}
// Provider casts a string to a normalized provider type.
func Provider(s string) ProviderType {
switch s {
case "", "-", "null", "nil", "0", "false":
return ProviderDefault
case "pass", "passwd", "password":
return ProviderLocal
case "ldap", "ad", "ldap/ad", "ldap\\ad":
return ProviderLDAP
default:
return ProviderType(clean.TypeLower(s))
}
}

View file

@ -6,10 +6,10 @@ import (
"github.com/stretchr/testify/assert"
)
func TestProviderString(t *testing.T) {
assert.Equal(t, "default", ProviderString(""))
assert.Equal(t, "default", ProviderString(ProviderDefault))
assert.Equal(t, "none", ProviderString(ProviderNone))
assert.Equal(t, "local", ProviderString(ProviderLocal))
assert.Equal(t, "ldap", ProviderString(ProviderLDAP))
func TestProviderType_String(t *testing.T) {
assert.Equal(t, "default", ProviderUnknown.String())
assert.Equal(t, "default", ProviderDefault.String())
assert.Equal(t, "none", ProviderNone.String())
assert.Equal(t, "local", ProviderLocal.String())
assert.Equal(t, "ldap", ProviderLDAP.String())
}

View file

@ -1,10 +1,10 @@
package report
const (
Enabled = "enabled"
Disabled = "disabled"
Yes = "yes"
No = "no"
Enabled = "Enabled"
Disabled = ""
Yes = "Yes"
No = ""
)
// Bool returns t or f, depending on the value of b.