mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-23 10:09:03 +02:00
More WIP
This commit is contained in:
@@ -0,0 +1,64 @@
|
|||||||
|
package appconfig
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// appConfigCtxKey is the context key used to store the AppConfigResolver in the http.Request's context
|
||||||
|
type appConfigCtxKey struct{}
|
||||||
|
|
||||||
|
type appConfigResolver func(ctx context.Context) (*AppConfigModel, error)
|
||||||
|
|
||||||
|
// AppConfigMiddleware is a Gin middleware that makes the application configuration available to all downstream handlers through the request's context
|
||||||
|
type AppConfigMiddleware struct {
|
||||||
|
appConfigService *AppConfigService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAppConfigMiddleware(appConfigService *AppConfigService) *AppConfigMiddleware {
|
||||||
|
return &AppConfigMiddleware{
|
||||||
|
appConfigService: appConfigService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add returns a Gin middleware that stores an AppConfigResolver in the http.Request's context
|
||||||
|
// The resolver loads the application configuration lazily on the first call and caches it for the duration of the request
|
||||||
|
func (m *AppConfigMiddleware) Add() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
reqCtx := c.Request.Context()
|
||||||
|
|
||||||
|
// Create a cache for each request in the middleware's scope, so it's unique per each request
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
cfg *AppConfigModel
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
// Note: the resolver accepts a context argument, it doesn't use the request's own
|
||||||
|
// This can be used for example for tracing
|
||||||
|
resolver := appConfigResolver(func(ctx context.Context) (*AppConfigModel, error) {
|
||||||
|
once.Do(func() {
|
||||||
|
cfg, err = m.appConfigService.GetConfig(ctx)
|
||||||
|
})
|
||||||
|
return cfg, err
|
||||||
|
})
|
||||||
|
|
||||||
|
// Store the resolver in the request's context
|
||||||
|
c.Request = c.Request.WithContext(context.WithValue(reqCtx, appConfigCtxKey{}, resolver))
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromCtx retrieves the app config from the context
|
||||||
|
func FromCtx(ctx context.Context) (*AppConfigModel, error) {
|
||||||
|
resolver, ok := ctx.Value(appConfigCtxKey{}).(appConfigResolver)
|
||||||
|
if !ok || resolver == nil {
|
||||||
|
// Indicates a development-time error
|
||||||
|
return nil, errors.New("middleware AppConfigMiddleware was not registered for the handler")
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolver(ctx)
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/italypaleale/go-kit/utils"
|
"github.com/italypaleale/go-kit/utils"
|
||||||
|
|
||||||
@@ -14,52 +15,74 @@ import (
|
|||||||
|
|
||||||
type AppConfigModel struct {
|
type AppConfigModel struct {
|
||||||
// General
|
// General
|
||||||
AppName string `json:"appName" public:"true"`
|
AppName AppConfigValue `json:"appName" public:"true"`
|
||||||
SessionDuration string `json:"sessionDuration" type:"int"` // In minutes
|
SessionDuration AppConfigValue `json:"sessionDuration" type:"int"` // In minutes
|
||||||
HomePageURL string `json:"homePageUrl" public:"true"`
|
HomePageURL AppConfigValue `json:"homePageUrl" public:"true"`
|
||||||
EmailsVerified string `json:"emailsVerified" type:"bool"`
|
EmailsVerified AppConfigValue `json:"emailsVerified" type:"bool"`
|
||||||
AccentColor string `json:"accentColor" public:"true"`
|
AccentColor AppConfigValue `json:"accentColor" public:"true"`
|
||||||
DisableAnimations string `json:"disableAnimations" type:"bool" public:"true"`
|
DisableAnimations AppConfigValue `json:"disableAnimations" type:"bool" public:"true"`
|
||||||
AllowOwnAccountEdit string `json:"allowOwnAccountEdit" type:"bool" public:"true"`
|
AllowOwnAccountEdit AppConfigValue `json:"allowOwnAccountEdit" type:"bool" public:"true"`
|
||||||
AllowUserSignups string `json:"allowUserSignups" public:"true"`
|
AllowUserSignups AppConfigValue `json:"allowUserSignups" public:"true"`
|
||||||
|
|
||||||
SignupDefaultUserGroupIDs string `json:"signupDefaultUserGroupIDs"` // JSON-encoded array of strings
|
SignupDefaultUserGroupIDs AppConfigValue `json:"signupDefaultUserGroupIDs"` // JSON-encoded array of strings
|
||||||
SignupDefaultCustomClaims string `json:"signupDefaultCustomClaims"` // JSON-encoded array of {key:string,value:string}
|
SignupDefaultCustomClaims AppConfigValue `json:"signupDefaultCustomClaims"` // JSON-encoded array of {key:string,value:string}
|
||||||
// Email
|
// Email
|
||||||
RequireUserEmail string `json:"requireUserEmail" type:"bool" public:"true"`
|
RequireUserEmail AppConfigValue `json:"requireUserEmail" type:"bool" public:"true"`
|
||||||
SmtpHost string `json:"smtpHost"`
|
SmtpHost AppConfigValue `json:"smtpHost"`
|
||||||
SmtpPort string `json:"smtpPort"`
|
SmtpPort AppConfigValue `json:"smtpPort"`
|
||||||
SmtpFrom string `json:"smtpFrom"`
|
SmtpFrom AppConfigValue `json:"smtpFrom"`
|
||||||
SmtpUser string `json:"smtpUser"`
|
SmtpUser AppConfigValue `json:"smtpUser"`
|
||||||
SmtpPassword string `json:"smtpPassword" sensitive:"true"`
|
SmtpPassword AppConfigValue `json:"smtpPassword" sensitive:"true"`
|
||||||
SmtpTls string `json:"smtpTls"`
|
SmtpTls AppConfigValue `json:"smtpTls"`
|
||||||
SmtpSkipCertVerify string `json:"smtpSkipCertVerify" type:"bool"`
|
SmtpSkipCertVerify AppConfigValue `json:"smtpSkipCertVerify" type:"bool"`
|
||||||
EmailLoginNotificationEnabled string `json:"emailLoginNotificationEnabled" type:"bool"`
|
EmailLoginNotificationEnabled AppConfigValue `json:"emailLoginNotificationEnabled" type:"bool"`
|
||||||
EmailOneTimeAccessAsUnauthenticatedEnabled string `json:"emailOneTimeAccessAsUnauthenticatedEnabled" type:"bool" public:"true"`
|
EmailOneTimeAccessAsUnauthenticatedEnabled AppConfigValue `json:"emailOneTimeAccessAsUnauthenticatedEnabled" type:"bool" public:"true"`
|
||||||
EmailOneTimeAccessAsAdminEnabled string `json:"emailOneTimeAccessAsAdminEnabled" type:"bool" public:"true"`
|
EmailOneTimeAccessAsAdminEnabled AppConfigValue `json:"emailOneTimeAccessAsAdminEnabled" type:"bool" public:"true"`
|
||||||
EmailApiKeyExpirationEnabled string `json:"emailApiKeyExpirationEnabled" type:"bool"`
|
EmailApiKeyExpirationEnabled AppConfigValue `json:"emailApiKeyExpirationEnabled" type:"bool"`
|
||||||
EmailVerificationEnabled string `json:"emailVerificationEnabled" type:"bool" public:"true"`
|
EmailVerificationEnabled AppConfigValue `json:"emailVerificationEnabled" type:"bool" public:"true"`
|
||||||
// LDAP
|
// LDAP
|
||||||
LdapEnabled string `json:"ldapEnabled" type:"bool" public:"true"`
|
LdapEnabled AppConfigValue `json:"ldapEnabled" type:"bool" public:"true"`
|
||||||
LdapUrl string `json:"ldapUrl"`
|
LdapUrl AppConfigValue `json:"ldapUrl"`
|
||||||
LdapBindDn string `json:"ldapBindDn"`
|
LdapBindDn AppConfigValue `json:"ldapBindDn"`
|
||||||
LdapBindPassword string `json:"ldapBindPassword" sensitive:"true"`
|
LdapBindPassword AppConfigValue `json:"ldapBindPassword" sensitive:"true"`
|
||||||
LdapBase string `json:"ldapBase"`
|
LdapBase AppConfigValue `json:"ldapBase"`
|
||||||
LdapUserSearchFilter string `json:"ldapUserSearchFilter"`
|
LdapUserSearchFilter AppConfigValue `json:"ldapUserSearchFilter"`
|
||||||
LdapUserGroupSearchFilter string `json:"ldapUserGroupSearchFilter"`
|
LdapUserGroupSearchFilter AppConfigValue `json:"ldapUserGroupSearchFilter"`
|
||||||
LdapSkipCertVerify string `json:"ldapSkipCertVerify" type:"bool"`
|
LdapSkipCertVerify AppConfigValue `json:"ldapSkipCertVerify" type:"bool"`
|
||||||
LdapAttributeUserUniqueIdentifier string `json:"ldapAttributeUserUniqueIdentifier"`
|
LdapAttributeUserUniqueIdentifier AppConfigValue `json:"ldapAttributeUserUniqueIdentifier"`
|
||||||
LdapAttributeUserUsername string `json:"ldapAttributeUserUsername"`
|
LdapAttributeUserUsername AppConfigValue `json:"ldapAttributeUserUsername"`
|
||||||
LdapAttributeUserEmail string `json:"ldapAttributeUserEmail"`
|
LdapAttributeUserEmail AppConfigValue `json:"ldapAttributeUserEmail"`
|
||||||
LdapAttributeUserFirstName string `json:"ldapAttributeUserFirstName"`
|
LdapAttributeUserFirstName AppConfigValue `json:"ldapAttributeUserFirstName"`
|
||||||
LdapAttributeUserLastName string `json:"ldapAttributeUserLastName"`
|
LdapAttributeUserLastName AppConfigValue `json:"ldapAttributeUserLastName"`
|
||||||
LdapAttributeUserDisplayName string `json:"ldapAttributeUserDisplayName"`
|
LdapAttributeUserDisplayName AppConfigValue `json:"ldapAttributeUserDisplayName"`
|
||||||
LdapAttributeUserProfilePicture string `json:"ldapAttributeUserProfilePicture"`
|
LdapAttributeUserProfilePicture AppConfigValue `json:"ldapAttributeUserProfilePicture"`
|
||||||
LdapAttributeGroupMember string `json:"ldapAttributeGroupMember"`
|
LdapAttributeGroupMember AppConfigValue `json:"ldapAttributeGroupMember"`
|
||||||
LdapAttributeGroupUniqueIdentifier string `json:"ldapAttributeGroupUniqueIdentifier"`
|
LdapAttributeGroupUniqueIdentifier AppConfigValue `json:"ldapAttributeGroupUniqueIdentifier"`
|
||||||
LdapAttributeGroupName string `json:"ldapAttributeGroupName"`
|
LdapAttributeGroupName AppConfigValue `json:"ldapAttributeGroupName"`
|
||||||
LdapAdminGroupName string `json:"ldapAdminGroupName"`
|
LdapAdminGroupName AppConfigValue `json:"ldapAdminGroupName"`
|
||||||
LdapSoftDeleteUsers string `json:"ldapSoftDeleteUsers" type:"bool"`
|
LdapSoftDeleteUsers AppConfigValue `json:"ldapSoftDeleteUsers" type:"bool"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppConfigValue holds a value
|
||||||
|
type AppConfigValue string
|
||||||
|
|
||||||
|
// IsTrue returns true if the value is a truthy string, such as "true", "t", "yes", "1", etc.
|
||||||
|
func (a AppConfigValue) IsTrue() bool {
|
||||||
|
return utils.IsTruthy(string(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsDurationMinutes returns the value as a time.Duration, interpreting the string as a whole number of minutes.
|
||||||
|
func (a AppConfigValue) AsDurationMinutes() time.Duration {
|
||||||
|
val, err := strconv.Atoi(string(a))
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return time.Duration(val) * time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements fmt.Stringer
|
||||||
|
func (a AppConfigValue) String() string {
|
||||||
|
return string(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultConfig() *AppConfigModel {
|
func getDefaultConfig() *AppConfigModel {
|
||||||
|
|||||||
@@ -96,11 +96,6 @@ func (s *AppConfigService) GetConfig(parentCtx context.Context) (*AppConfigModel
|
|||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE
|
|
||||||
func (s *AppConfigService) GetDbConfig() *model.AppConfig {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AppConfigService) updateAppConfigUpdateDatabase(ctx context.Context, tx *gorm.DB, dbUpdate *[]model.AppConfigVariable) error {
|
func (s *AppConfigService) updateAppConfigUpdateDatabase(ctx context.Context, tx *gorm.DB, dbUpdate *[]model.AppConfigVariable) error {
|
||||||
err := tx.
|
err := tx.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -509,6 +510,12 @@ func (uc *UserController) RequestOneTimeAccessEmailAsAdminHandler(c *gin.Context
|
|||||||
// @Success 200 {object} dto.UserDto
|
// @Success 200 {object} dto.UserDto
|
||||||
// @Router /api/one-time-access-token/{token} [post]
|
// @Router /api/one-time-access-token/{token} [post]
|
||||||
func (uc *UserController) exchangeOneTimeAccessTokenHandler(c *gin.Context) {
|
func (uc *UserController) exchangeOneTimeAccessTokenHandler(c *gin.Context) {
|
||||||
|
cfg, err := appconfig.FromCtx(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
_ = c.Error(fmt.Errorf("error loading app configuration: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
loginCode := c.Param("token")
|
loginCode := c.Param("token")
|
||||||
// reject invalid length login codes
|
// reject invalid length login codes
|
||||||
if len(loginCode) != 6 && len(loginCode) != 16 {
|
if len(loginCode) != 6 && len(loginCode) != 16 {
|
||||||
@@ -524,12 +531,13 @@ func (uc *UserController) exchangeOneTimeAccessTokenHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userDto dto.UserDto
|
var userDto dto.UserDto
|
||||||
if err := dto.MapStruct(user, &userDto); err != nil {
|
err = dto.MapStruct(user, &userDto)
|
||||||
|
if err != nil {
|
||||||
_ = c.Error(err)
|
_ = c.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
maxAge := int(uc.appConfigService.GetDbConfig().SessionDuration.AsDurationMinutes().Seconds())
|
maxAge := int(cfg.SessionDuration.AsDurationMinutes().Seconds())
|
||||||
cookie.AddAccessTokenCookie(c, maxAge, token)
|
cookie.AddAccessTokenCookie(c, maxAge, token)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, userDto)
|
c.JSON(http.StatusOK, userDto)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ type LdapService struct {
|
|||||||
userService *UserService
|
userService *UserService
|
||||||
groupService *UserGroupService
|
groupService *UserGroupService
|
||||||
fileStorage storage.FileStorage
|
fileStorage storage.FileStorage
|
||||||
clientFactory func() (ldapClient, error)
|
clientFactory func(ctx context.Context) (ldapClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type savePicture struct {
|
type savePicture struct {
|
||||||
@@ -84,15 +84,18 @@ func NewLdapService(db *gorm.DB, httpClient *http.Client, appConfigService *appc
|
|||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LdapService) createClient() (ldapClient, error) {
|
func (s *LdapService) createClient(ctx context.Context) (ldapClient, error) {
|
||||||
dbConfig := s.appConfigService.GetDbConfig()
|
dbConfig, err := appconfig.FromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error loading app configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if !dbConfig.LdapEnabled.IsTrue() {
|
if !dbConfig.LdapEnabled.IsTrue() {
|
||||||
return nil, fmt.Errorf("LDAP is not enabled")
|
return nil, fmt.Errorf("LDAP is not enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup LDAP connection
|
// Setup LDAP connection
|
||||||
client, err := ldap.DialURL(dbConfig.LdapUrl.Value, ldap.DialWithTLSConfig(&tls.Config{
|
client, err := ldap.DialURL(dbConfig.LdapUrl.String(), ldap.DialWithTLSConfig(&tls.Config{
|
||||||
InsecureSkipVerify: dbConfig.LdapSkipCertVerify.IsTrue(), //nolint:gosec
|
InsecureSkipVerify: dbConfig.LdapSkipCertVerify.IsTrue(), //nolint:gosec
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -100,7 +103,7 @@ func (s *LdapService) createClient() (ldapClient, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bind as service account
|
// Bind as service account
|
||||||
err = client.Bind(dbConfig.LdapBindDn.Value, dbConfig.LdapBindPassword.Value)
|
err = client.Bind(dbConfig.LdapBindDn.String(), dbConfig.LdapBindPassword.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to bind to LDAP: %w", err)
|
return nil, fmt.Errorf("failed to bind to LDAP: %w", err)
|
||||||
}
|
}
|
||||||
@@ -196,13 +199,13 @@ func (s *LdapService) fetchDesiredState(ctx context.Context, client ldapClient)
|
|||||||
|
|
||||||
func (s *LdapService) applyAdminGroupMembership(desiredUsers []ldapDesiredUser, desiredGroups []ldapDesiredGroup) {
|
func (s *LdapService) applyAdminGroupMembership(desiredUsers []ldapDesiredUser, desiredGroups []ldapDesiredGroup) {
|
||||||
dbConfig := s.appConfigService.GetDbConfig()
|
dbConfig := s.appConfigService.GetDbConfig()
|
||||||
if dbConfig.LdapAdminGroupName.Value == "" {
|
if dbConfig.LdapAdminGroupName == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
adminUsernames := make(map[string]struct{})
|
adminUsernames := make(map[string]struct{})
|
||||||
for _, group := range desiredGroups {
|
for _, group := range desiredGroups {
|
||||||
if group.input.Name != dbConfig.LdapAdminGroupName.Value {
|
if group.input.Name != dbConfig.LdapAdminGroupName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +262,7 @@ func (s *LdapService) fetchGroupsFromLDAP(ctx context.Context, client ldapClient
|
|||||||
groupMembers := value.GetAttributeValues(dbConfig.LdapAttributeGroupMember.Value)
|
groupMembers := value.GetAttributeValues(dbConfig.LdapAttributeGroupMember.Value)
|
||||||
memberUsernames := make([]string, 0, len(groupMembers))
|
memberUsernames := make([]string, 0, len(groupMembers))
|
||||||
for _, member := range groupMembers {
|
for _, member := range groupMembers {
|
||||||
username := s.resolveGroupMemberUsername(ctx, client, member, usernamesByDN)
|
username := s.resolveGroupMemberUsername(ctx, client, member, usernamesByDN, dbConfig.LdapAttributeUserUsername.Value)
|
||||||
if username == "" {
|
if username == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -291,27 +294,30 @@ func (s *LdapService) fetchGroupsFromLDAP(ctx context.Context, client ldapClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LdapService) fetchUsersFromLDAP(ctx context.Context, client ldapClient) (desiredUsers []ldapDesiredUser, ldapUserIDs map[string]struct{}, usernamesByDN map[string]string, err error) {
|
func (s *LdapService) fetchUsersFromLDAP(ctx context.Context, client ldapClient) (desiredUsers []ldapDesiredUser, ldapUserIDs map[string]struct{}, usernamesByDN map[string]string, err error) {
|
||||||
dbConfig := s.appConfigService.GetDbConfig()
|
dbConfig, err := appconfig.FromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("error loading app configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Query LDAP for all users we want to manage
|
// Query LDAP for all users we want to manage
|
||||||
searchAttrs := []string{
|
searchAttrs := []string{
|
||||||
"sn",
|
"sn",
|
||||||
"cn",
|
"cn",
|
||||||
dbConfig.LdapAttributeUserUniqueIdentifier.Value,
|
dbConfig.LdapAttributeUserUniqueIdentifier.String(),
|
||||||
dbConfig.LdapAttributeUserUsername.Value,
|
dbConfig.LdapAttributeUserUsername.String(),
|
||||||
dbConfig.LdapAttributeUserEmail.Value,
|
dbConfig.LdapAttributeUserEmail.String(),
|
||||||
dbConfig.LdapAttributeUserFirstName.Value,
|
dbConfig.LdapAttributeUserFirstName.String(),
|
||||||
dbConfig.LdapAttributeUserLastName.Value,
|
dbConfig.LdapAttributeUserLastName.String(),
|
||||||
dbConfig.LdapAttributeUserProfilePicture.Value,
|
dbConfig.LdapAttributeUserProfilePicture.String(),
|
||||||
dbConfig.LdapAttributeUserDisplayName.Value,
|
dbConfig.LdapAttributeUserDisplayName.String(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters must start and finish with ()!
|
// Filters must start and finish with ()!
|
||||||
searchReq := ldap.NewSearchRequest(
|
searchReq := ldap.NewSearchRequest(
|
||||||
dbConfig.LdapBase.Value,
|
dbConfig.LdapBase.String(),
|
||||||
ldap.ScopeWholeSubtree,
|
ldap.ScopeWholeSubtree,
|
||||||
0, 0, 0, false,
|
0, 0, 0, false,
|
||||||
dbConfig.LdapUserSearchFilter.Value,
|
dbConfig.LdapUserSearchFilter.String(),
|
||||||
searchAttrs,
|
searchAttrs,
|
||||||
[]ldap.Control{},
|
[]ldap.Control{},
|
||||||
)
|
)
|
||||||
@@ -327,28 +333,28 @@ func (s *LdapService) fetchUsersFromLDAP(ctx context.Context, client ldapClient)
|
|||||||
desiredUsers = make([]ldapDesiredUser, 0, len(result.Entries))
|
desiredUsers = make([]ldapDesiredUser, 0, len(result.Entries))
|
||||||
|
|
||||||
for _, value := range result.Entries {
|
for _, value := range result.Entries {
|
||||||
username := norm.NFC.String(value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value))
|
username := norm.NFC.String(value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.String()))
|
||||||
if normalizedDN := normalizeLDAPDN(value.DN); normalizedDN != "" && username != "" {
|
if normalizedDN := normalizeLDAPDN(value.DN); normalizedDN != "" && username != "" {
|
||||||
usernamesByDN[normalizedDN] = username
|
usernamesByDN[normalizedDN] = username
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapID := convertLdapIdToString(value.GetAttributeValue(dbConfig.LdapAttributeUserUniqueIdentifier.Value))
|
ldapID := convertLdapIdToString(value.GetAttributeValue(dbConfig.LdapAttributeUserUniqueIdentifier.String()))
|
||||||
|
|
||||||
// Skip users without a valid LDAP ID
|
// Skip users without a valid LDAP ID
|
||||||
if ldapID == "" {
|
if ldapID == "" {
|
||||||
slog.Warn("Skipping LDAP user without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeUserUniqueIdentifier.Value))
|
slog.Warn("Skipping LDAP user without a valid unique identifier", slog.String("attribute", dbConfig.LdapAttributeUserUniqueIdentifier.String()))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapUserIDs[ldapID] = struct{}{}
|
ldapUserIDs[ldapID] = struct{}{}
|
||||||
|
|
||||||
newUser := dto.UserCreateDto{
|
newUser := dto.UserCreateDto{
|
||||||
Username: value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value),
|
Username: value.GetAttributeValue(dbConfig.LdapAttributeUserUsername.String()),
|
||||||
Email: utils.PtrOrNil(value.GetAttributeValue(dbConfig.LdapAttributeUserEmail.Value)),
|
Email: utils.PtrOrNil(value.GetAttributeValue(dbConfig.LdapAttributeUserEmail.String())),
|
||||||
EmailVerified: true,
|
EmailVerified: true,
|
||||||
FirstName: value.GetAttributeValue(dbConfig.LdapAttributeUserFirstName.Value),
|
FirstName: value.GetAttributeValue(dbConfig.LdapAttributeUserFirstName.String()),
|
||||||
LastName: value.GetAttributeValue(dbConfig.LdapAttributeUserLastName.Value),
|
LastName: value.GetAttributeValue(dbConfig.LdapAttributeUserLastName.String()),
|
||||||
DisplayName: value.GetAttributeValue(dbConfig.LdapAttributeUserDisplayName.Value),
|
DisplayName: value.GetAttributeValue(dbConfig.LdapAttributeUserDisplayName.String()),
|
||||||
// Admin status is computed after groups are loaded so it can use the
|
// Admin status is computed after groups are loaded so it can use the
|
||||||
// configured group member attribute instead of a hard-coded memberOf.
|
// configured group member attribute instead of a hard-coded memberOf.
|
||||||
IsAdmin: false,
|
IsAdmin: false,
|
||||||
@@ -370,16 +376,14 @@ func (s *LdapService) fetchUsersFromLDAP(ctx context.Context, client ldapClient)
|
|||||||
desiredUsers = append(desiredUsers, ldapDesiredUser{
|
desiredUsers = append(desiredUsers, ldapDesiredUser{
|
||||||
ldapID: ldapID,
|
ldapID: ldapID,
|
||||||
input: newUser,
|
input: newUser,
|
||||||
picture: value.GetAttributeValue(dbConfig.LdapAttributeUserProfilePicture.Value),
|
picture: value.GetAttributeValue(dbConfig.LdapAttributeUserProfilePicture.String()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return desiredUsers, ldapUserIDs, usernamesByDN, nil
|
return desiredUsers, ldapUserIDs, usernamesByDN, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client ldapClient, member string, usernamesByDN map[string]string) string {
|
func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client ldapClient, member string, usernamesByDN map[string]string, usernameAttr string) string {
|
||||||
dbConfig := s.appConfigService.GetDbConfig()
|
|
||||||
|
|
||||||
// First try the DN cache we built while loading users
|
// First try the DN cache we built while loading users
|
||||||
username, exists := usernamesByDN[normalizeLDAPDN(member)]
|
username, exists := usernamesByDN[normalizeLDAPDN(member)]
|
||||||
if exists && username != "" {
|
if exists && username != "" {
|
||||||
@@ -387,14 +391,15 @@ func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client lda
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then try to extract the username directly from the DN
|
// Then try to extract the username directly from the DN
|
||||||
username = getDNProperty(dbConfig.LdapAttributeUserUsername.Value, member)
|
username = getDNProperty(usernameAttr, member)
|
||||||
if username != "" {
|
if username != "" {
|
||||||
return norm.NFC.String(username)
|
return norm.NFC.String(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
// posixGroup (and similar) stores bare usernames in memberUid, not DNs. Treat any value
|
// posixGroup (and similar) stores bare usernames in memberUid, not DNs. Treat any value
|
||||||
// that is not a valid DN as the username directly — see https://github.com/pocket-id/pocket-id/issues/1408
|
// that is not a valid DN as the username directly — see https://github.com/pocket-id/pocket-id/issues/1408
|
||||||
if _, err := ldap.ParseDN(member); err != nil {
|
_, err := ldap.ParseDN(member)
|
||||||
|
if err != nil {
|
||||||
return norm.NFC.String(member)
|
return norm.NFC.String(member)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,7 +409,7 @@ func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client lda
|
|||||||
ldap.ScopeBaseObject,
|
ldap.ScopeBaseObject,
|
||||||
0, 0, 0, false,
|
0, 0, 0, false,
|
||||||
"(objectClass=*)",
|
"(objectClass=*)",
|
||||||
[]string{dbConfig.LdapAttributeUserUsername.Value},
|
[]string{usernameAttr},
|
||||||
[]ldap.Control{},
|
[]ldap.Control{},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -414,7 +419,7 @@ func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client lda
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
username = userResult.Entries[0].GetAttributeValue(dbConfig.LdapAttributeUserUsername.Value)
|
username = userResult.Entries[0].GetAttributeValue(usernameAttr)
|
||||||
if username == "" {
|
if username == "" {
|
||||||
slog.WarnContext(ctx, "Could not extract username from group member DN", slog.String("member", member))
|
slog.WarnContext(ctx, "Could not extract username from group member DN", slog.String("member", member))
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
|
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
|
||||||
@@ -64,13 +65,18 @@ func (s *UserGroupService) getInternal(ctx context.Context, id string, tx *gorm.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserGroupService) Delete(ctx context.Context, id string) error {
|
func (s *UserGroupService) Delete(ctx context.Context, id string) error {
|
||||||
|
cfg, err := appconfig.FromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading app configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
tx := s.db.Begin()
|
tx := s.db.Begin()
|
||||||
defer func() {
|
defer func() {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var group model.UserGroup
|
var group model.UserGroup
|
||||||
err := tx.
|
err = tx.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
Where("id = ?", id).
|
Where("id = ?", id).
|
||||||
First(&group).
|
First(&group).
|
||||||
@@ -80,7 +86,7 @@ func (s *UserGroupService) Delete(ctx context.Context, id string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Disallow deleting the group if it is an LDAP group and LDAP is enabled
|
// Disallow deleting the group if it is an LDAP group and LDAP is enabled
|
||||||
if group.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue() {
|
if group.LdapID != nil && cfg.LdapEnabled.IsTrue() {
|
||||||
return &common.LdapUserGroupUpdateError{}
|
return &common.LdapUserGroupUpdateError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,10 +129,9 @@ func (s *UserGroupService) createInternal(ctx context.Context, input dto.UserGro
|
|||||||
Preload("Users").
|
Preload("Users").
|
||||||
Create(&group).
|
Create(&group).
|
||||||
Error
|
Error
|
||||||
if err != nil {
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
return model.UserGroup{}, &common.AlreadyInUseError{Property: "name"}
|
||||||
return model.UserGroup{}, &common.AlreadyInUseError{Property: "name"}
|
} else if err != nil {
|
||||||
}
|
|
||||||
return model.UserGroup{}, err
|
return model.UserGroup{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,8 +167,13 @@ func (s *UserGroupService) updateInternal(ctx context.Context, id string, input
|
|||||||
return model.UserGroup{}, err
|
return model.UserGroup{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg, err := appconfig.FromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return model.UserGroup{}, fmt.Errorf("error loading app configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Disallow updating the group if it is an LDAP group and LDAP is enabled
|
// Disallow updating the group if it is an LDAP group and LDAP is enabled
|
||||||
if !isLdapSync && group.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue() {
|
if !isLdapSync && group.LdapID != nil && cfg.LdapEnabled.IsTrue() {
|
||||||
return model.UserGroup{}, &common.LdapUserGroupUpdateError{}
|
return model.UserGroup{}, &common.LdapUserGroupUpdateError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +227,7 @@ func (s *UserGroupService) updateUsersInternal(ctx context.Context, id string, u
|
|||||||
// Fetch the users based on the userIds
|
// Fetch the users based on the userIds
|
||||||
var users []model.User
|
var users []model.User
|
||||||
if len(userIds) > 0 {
|
if len(userIds) > 0 {
|
||||||
err := tx.
|
err = tx.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
Where("id IN (?)", userIds).
|
Where("id IN (?)", userIds).
|
||||||
Find(&users).
|
Find(&users).
|
||||||
|
|||||||
@@ -13,17 +13,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
|
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/utils/email"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
|
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/common"
|
"github.com/pocket-id/pocket-id/backend/internal/common"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/model"
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
||||||
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/storage"
|
"github.com/pocket-id/pocket-id/backend/internal/storage"
|
||||||
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils/email"
|
||||||
profilepicture "github.com/pocket-id/pocket-id/backend/internal/utils/image"
|
profilepicture "github.com/pocket-id/pocket-id/backend/internal/utils/image"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -205,9 +205,13 @@ func (s *UserService) DeleteUser(ctx context.Context, userID string, allowLdapDe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) deleteUserInternal(ctx context.Context, tx *gorm.DB, userID string, allowLdapDelete bool) error {
|
func (s *UserService) deleteUserInternal(ctx context.Context, tx *gorm.DB, userID string, allowLdapDelete bool) error {
|
||||||
var user model.User
|
cfg, err := appconfig.FromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading app configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
err := tx.
|
var user model.User
|
||||||
|
err = tx.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
Where("id = ?", userID).
|
Where("id = ?", userID).
|
||||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
@@ -218,7 +222,7 @@ func (s *UserService) deleteUserInternal(ctx context.Context, tx *gorm.DB, userI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Disallow deleting the user if it is an LDAP user, LDAP is enabled, and the user is not disabled
|
// Disallow deleting the user if it is an LDAP user, LDAP is enabled, and the user is not disabled
|
||||||
if !allowLdapDelete && !user.Disabled && user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue() {
|
if !allowLdapDelete && !user.Disabled && user.LdapID != nil && cfg.LdapEnabled.IsTrue() {
|
||||||
return &common.LdapUserUpdateError{}
|
return &common.LdapUserUpdateError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,7 +258,12 @@ func (s *UserService) CreateUser(ctx context.Context, input dto.UserCreateDto) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCreateDto, isLdapSync bool, tx *gorm.DB) (model.User, error) {
|
func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCreateDto, isLdapSync bool, tx *gorm.DB) (model.User, error) {
|
||||||
if s.appConfigService.GetDbConfig().RequireUserEmail.IsTrue() && input.Email == nil {
|
cfg, err := appconfig.FromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return model.User{}, fmt.Errorf("error loading app configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.RequireUserEmail.IsTrue() && input.Email == nil {
|
||||||
return model.User{}, &common.UserEmailNotSetError{}
|
return model.User{}, &common.UserEmailNotSetError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +295,7 @@ func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCrea
|
|||||||
user.LdapID = &input.LdapID
|
user.LdapID = &input.LdapID
|
||||||
}
|
}
|
||||||
|
|
||||||
err := tx.WithContext(ctx).Create(&user).Error
|
err = tx.WithContext(ctx).Create(&user).Error
|
||||||
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
if errors.Is(err, gorm.ErrDuplicatedKey) {
|
||||||
// Do not follow this path if we're using LDAP, as we don't want to roll-back the transaction here
|
// Do not follow this path if we're using LDAP, as we don't want to roll-back the transaction here
|
||||||
if !isLdapSync {
|
if !isLdapSync {
|
||||||
@@ -305,12 +314,14 @@ func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCrea
|
|||||||
// Apply default groups and claims for new non-LDAP users
|
// Apply default groups and claims for new non-LDAP users
|
||||||
if !isLdapSync {
|
if !isLdapSync {
|
||||||
if len(input.UserGroupIds) == 0 {
|
if len(input.UserGroupIds) == 0 {
|
||||||
if err := s.applyDefaultGroups(ctx, &user, tx); err != nil {
|
err = s.applyDefaultGroups(ctx, &user, tx)
|
||||||
|
if err != nil {
|
||||||
return model.User{}, err
|
return model.User{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.applyDefaultCustomClaims(ctx, &user, tx); err != nil {
|
err = s.applyDefaultCustomClaims(ctx, &user, tx)
|
||||||
|
if err != nil {
|
||||||
return model.User{}, err
|
return model.User{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,12 +334,15 @@ func (s *UserService) CreateUserInternal(ctx context.Context, input dto.UserCrea
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) applyDefaultGroups(ctx context.Context, user *model.User, tx *gorm.DB) error {
|
func (s *UserService) applyDefaultGroups(ctx context.Context, user *model.User, tx *gorm.DB) error {
|
||||||
config := s.appConfigService.GetDbConfig()
|
cfg, err := appconfig.FromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading app configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var groupIDs []string
|
var groupIDs []string
|
||||||
v := config.SignupDefaultUserGroupIDs.Value
|
v := cfg.SignupDefaultUserGroupIDs
|
||||||
if v != "" && v != "[]" {
|
if v != "" && v != "[]" {
|
||||||
err := json.Unmarshal([]byte(v), &groupIDs)
|
err = json.Unmarshal([]byte(v), &groupIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid SignupDefaultUserGroupIDs JSON: %w", err)
|
return fmt.Errorf("invalid SignupDefaultUserGroupIDs JSON: %w", err)
|
||||||
}
|
}
|
||||||
@@ -355,12 +369,15 @@ func (s *UserService) applyDefaultGroups(ctx context.Context, user *model.User,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) applyDefaultCustomClaims(ctx context.Context, user *model.User, tx *gorm.DB) error {
|
func (s *UserService) applyDefaultCustomClaims(ctx context.Context, user *model.User, tx *gorm.DB) error {
|
||||||
config := s.appConfigService.GetDbConfig()
|
cfg, err := appconfig.FromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error loading app configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var claims []dto.CustomClaimCreateDto
|
var claims []dto.CustomClaimCreateDto
|
||||||
v := config.SignupDefaultCustomClaims.Value
|
v := cfg.SignupDefaultCustomClaims
|
||||||
if v != "" && v != "[]" {
|
if v != "" && v != "[]" {
|
||||||
err := json.Unmarshal([]byte(v), &claims)
|
err = json.Unmarshal([]byte(v), &claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid SignupDefaultCustomClaims JSON: %w", err)
|
return fmt.Errorf("invalid SignupDefaultCustomClaims JSON: %w", err)
|
||||||
}
|
}
|
||||||
@@ -395,12 +412,17 @@ func (s *UserService) UpdateUser(ctx context.Context, userID string, updatedUser
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) updateUserInternal(ctx context.Context, userID string, updatedUser dto.UserCreateDto, updateOwnUser bool, isLdapSync bool, tx *gorm.DB) (model.User, error) {
|
func (s *UserService) updateUserInternal(ctx context.Context, userID string, updatedUser dto.UserCreateDto, updateOwnUser bool, isLdapSync bool, tx *gorm.DB) (model.User, error) {
|
||||||
if s.appConfigService.GetDbConfig().RequireUserEmail.IsTrue() && updatedUser.Email == nil {
|
cfg, err := appconfig.FromCtx(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return model.User{}, fmt.Errorf("error loading app configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.RequireUserEmail.IsTrue() && updatedUser.Email == nil {
|
||||||
return model.User{}, &common.UserEmailNotSetError{}
|
return model.User{}, &common.UserEmailNotSetError{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var user model.User
|
var user model.User
|
||||||
err := tx.
|
err = tx.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
Where("id = ?", userID).
|
Where("id = ?", userID).
|
||||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||||
@@ -411,8 +433,8 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if this is an LDAP user and LDAP is enabled
|
// Check if this is an LDAP user and LDAP is enabled
|
||||||
isLdapUser := user.LdapID != nil && s.appConfigService.GetDbConfig().LdapEnabled.IsTrue()
|
isLdapUser := user.LdapID != nil && cfg.LdapEnabled.IsTrue()
|
||||||
allowOwnAccountEdit := s.appConfigService.GetDbConfig().AllowOwnAccountEdit.IsTrue()
|
allowOwnAccountEdit := cfg.AllowOwnAccountEdit.IsTrue()
|
||||||
|
|
||||||
if !isLdapSync && (isLdapUser || (!allowOwnAccountEdit && updateOwnUser)) {
|
if !isLdapSync && (isLdapUser || (!allowOwnAccountEdit && updateOwnUser)) {
|
||||||
// Restricted update: Only locale can be changed when:
|
// Restricted update: Only locale can be changed when:
|
||||||
@@ -430,7 +452,7 @@ func (s *UserService) updateUserInternal(ctx context.Context, userID string, upd
|
|||||||
|
|
||||||
if (user.Email == nil && updatedUser.Email != nil) || (user.Email != nil && updatedUser.Email != nil && *user.Email != *updatedUser.Email) {
|
if (user.Email == nil && updatedUser.Email != nil) || (user.Email != nil && updatedUser.Email != nil && *user.Email != *updatedUser.Email) {
|
||||||
// Email has changed, reset email verification status
|
// Email has changed, reset email verification status
|
||||||
user.EmailVerified = s.appConfigService.GetDbConfig().EmailsVerified.IsTrue()
|
user.EmailVerified = cfg.EmailsVerified.IsTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Email = updatedUser.Email
|
user.Email = updatedUser.Email
|
||||||
|
|||||||
Reference in New Issue
Block a user