authentricity/internal/models/user.go

170 lines
4.8 KiB
Go

package models
import (
"encoding/json"
"errors"
"time"
"github.com/GehirnInc/crypt"
_ "github.com/GehirnInc/crypt/sha256_crypt"
_ "github.com/GehirnInc/crypt/sha512_crypt"
"github.com/google/uuid"
)
type Disposition string
const (
DispositionIntrinsic Disposition = "intrinsic"
DispositionSystem Disposition = "system"
DispositionDynamic Disposition = "dynamic"
DispositionRegular Disposition = "regular"
DispositionContainer Disposition = "container"
DispositionReserved Disposition = "reserved"
)
type ResourceLimit struct {
Cur uint64 `mapstructure:"cur"`
Max uint64 `mapstructure:"max"`
}
type UserRecord struct {
UUID uuid.UUID `mapstructure:"uuid"`
UserName string `mapstructure:"userName"`
Realm string `mapstructure:"realm,omitempty"`
RealName string `mapstructure:"realName,omitempty"`
EmailAddress string `mapstructure:"emailAddress,omitempty"`
IconName string `mapstructure:"iconName,omitempty"`
Location string `mapstructure:"location,omitempty"`
Disposition Disposition `mapstructure:"disposition,omitempty"`
Shell string `mapstructure:"shell,omitempty"`
Ummask *int32 `mapstructure:"umask,omitempty"`
Environment []string `mapstructure:"environment,omitempty"`
TimeZone string `mapstructure:"timeZone,omitempty"`
PreferredLanguage string `mapstructure:"preferredLanguage,omitempty"`
NiceLevel int32 `mapstructure:"niceLevel,omitempty"`
ResourceLimits map[string]ResourceLimit `mapstructure:"resourceLimits,omitempty"`
Locked bool `mapstructure:"locked,omitempty"`
NotBeforeUSec *int64 `mapstructure:"notBeforeUSec,omitempty"`
NotAfterUSec *int64 `mapstructure:"notAfterUSec,omitempty"`
HomeDirectory string `mapstructure:"homeDirectory,omitempty"`
UID uint32 `mapstructure:"uid,omitempty"`
GID uint32 `mapstructure:"gid,omitempty"`
Service string `mapstructure:"service,omitempty"`
Privileged *UserPrivileged `mapstructure:"privileged,omitempty"`
Other map[string]interface{} `mapstructure:"-,remain"`
}
func (ur *UserRecord) ID() uuid.UUID {
return ur.UUID
}
func (ur *UserRecord) SetID(id uuid.UUID) {
ur.UUID = id
}
func (ur *UserRecord) Type() EntityType {
return TypeUser
}
func (ur *UserRecord) Name() string {
return ur.UserName
}
func (ur *UserRecord) Email() string {
return ur.EmailAddress
}
func (ur *UserRecord) StripPrivileged() {
ur.Privileged = nil
}
func (ur *UserRecord) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{})
for k, v := range ur.Other {
m[k] = v
}
if err := decode(ur, &m); err != nil {
return nil, err
}
m["@type"] = string(TypeUser)
return json.Marshal(m)
}
func (ur *UserRecord) UnmarshalJSON(data []byte) error {
m := make(map[string]interface{})
if err := json.Unmarshal(data, m); err != nil {
return err
}
delete(m, "@type")
return decode(m, ur)
}
func (ur *UserRecord) SynthesizeGroup() *GroupRecord {
return &GroupRecord{
UUID: ur.UUID,
GroupName: ur.UserName,
Realm: ur.Realm,
Description: ur.RealName,
Disposition: ur.Disposition,
GID: ur.GID,
Service: ur.Service,
}
}
func (ur *UserRecord) IsUserAllowedToLogin() bool {
if ur.Locked {
return false
}
nowMicros := time.Now().UnixMicro()
if ur.NotBeforeUSec != nil && *ur.NotBeforeUSec > nowMicros {
return false
}
if ur.NotAfterUSec != nil && *ur.NotAfterUSec < nowMicros {
return false
}
return true
}
func (ur *UserRecord) EnsurePrivileged() *UserPrivileged {
if ur.Privileged == nil {
ur.Privileged = new(UserPrivileged)
}
return ur.Privileged
}
type UserPrivileged struct {
HashedPassword []string `mapstructure:"hashedPassword,omitempty"`
SSHAuthorizedKeys []string `mapstructure:"sshAuthorizedKeys,omitempty"`
PublicKeyCredentials []PublicKeyCredential `mapstructure:"publicKeyCredentials,omitempty"`
Other map[string]interface{} `mapstructure:"-,remain"`
}
func (priv *UserPrivileged) CheckPassword(pw string) bool {
if priv == nil {
return false
}
k := []byte(pw)
for _, possibleHash := range priv.HashedPassword {
if !crypt.IsHashSupported(possibleHash) {
continue
}
cr := crypt.NewFromHash(possibleHash)
err := cr.Verify(possibleHash, k)
if err == nil {
return true
} else if errors.Is(err, crypt.ErrKeyMismatch) {
return false
} else {
continue
}
}
return false
}