package ldapSync /* this file contains functions to sync ldap users and groups to the database */ import ( "fmt" "git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/logger" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/forms" "github.com/pocketbase/pocketbase/models" "os" "strings" ) // upsertLDAPGroup This function creates / updates a record in the ldap groups table func upsertLDAPGroup(app *pocketbase.PocketBase, ldapGroup *LDAPGroup) error { // find ldapGroups table collection, err := app.Dao().FindCollectionByNameOrId(ldapGroupsTableName) if err != nil { return err } var record *models.Record // if record exists, update it if res, _ := app.Dao().FindFirstRecordByFilter( ldapGroupsTableName, "objectGUID = {:objectGUID}", dbx.Params{"objectGUID": ldapGroup.objectGUID}, ); res != nil { record = res } else { // if record does not exist, create it record = models.NewRecord(collection) } form := forms.NewRecordUpsert(app, record) var memberOfGroupIds []string // get group ids from group dns for _, groupDn := range ldapGroup.memberOf { group, err := GetLdapGroupByDN(app, groupDn) if err == nil { memberOfGroupIds = append(memberOfGroupIds, group.Id) } else { logger.LogErrorF("unable to find %s.memberOf: %s", ldapGroup.cn, groupDn) } } // load data err = form.LoadData(map[string]any{ "objectGUID": ldapGroup.objectGUID, "cn": ldapGroup.cn, "dn": ldapGroup.dn, "description": ldapGroup.description, "memberOf": memberOfGroupIds, }) if err != nil { return err } // validate and submit (internally it calls app.Dao().SaveRecord(record) in a transaction) if err := form.Submit(); err != nil { return fmt.Errorf("failed to upsert group with dn: %s - %w", ldapGroup.dn, err) } return nil } // upsertLDAPUser This function creates / updates a record in the ldap users table func upsertLDAPUser(app *pocketbase.PocketBase, ldapUser *LDAPUser) error { // find ldapUsers table collection, err := app.Dao().FindCollectionByNameOrId(ldapUsersTableName) if err != nil { return err } var record *models.Record // if record exists, update it if res, _ := app.Dao().FindFirstRecordByFilter( ldapUsersTableName, "objectGUID = {:objectGUID}", dbx.Params{"objectGUID": ldapUser.objectGUID}, ); res != nil { record = res } else { // if record does not exist, create it record = models.NewRecord(collection) // refresh token key only if new record is created // if record is updated, the token key should not be changed because it is used to authenticate the user // if the token key is changed, the user will be logged out if err := record.RefreshTokenKey(); err != nil { return err } } // calculate accountExpires accountExpires, _ := ldapTimeToUnixTime(ldapUser.accountExpires) var memberOfGroupIds []string var isAdmin bool // get group ids from group dns for _, groupDn := range ldapUser.memberOf { // get group by dn group, err := GetLdapGroupByDN(app, groupDn) // check if error if err != nil { logger.LogErrorF("unable to find %s.memberOf: %s", ldapUser.cn, groupDn) continue } // check if user is in admin group if strings.ToLower(group.Get("dn").(string)) == strings.ToLower(os.Getenv("LDAP_ADMIN_GROUP_DN")) { isAdmin = true } // add group id to memberOfGroupIds memberOfGroupIds = append(memberOfGroupIds, group.Id) } // set data record.Set("objectGUID", ldapUser.objectGUID) record.Set("givenName", ldapUser.givenName) record.Set("sn", ldapUser.sn) record.Set("username", ldapUser.cn) record.Set("accountExpires", accountExpires) record.Set("email", ldapUser.mail) record.Set("emailVisibility", false) record.Set("verified", true) record.Set("dn", ldapUser.dn) record.Set("cn", ldapUser.cn) record.Set("memberOf", memberOfGroupIds) record.Set("REALM", ldapUser.REALM) record.Set("isAdmin", isAdmin) if err := app.Dao().SaveRecord(record); err != nil { return fmt.Errorf("failed to upsert user with dn: %s - %w", ldapUser.dn, err) } return nil } // GetLdapGroupByDN This function returns a record from the ldap groups table by dn. It also expands the memberOf field. func GetLdapGroupByDN(app *pocketbase.PocketBase, dn string) (*models.Record, error) { record, err := app.Dao().FindFirstRecordByFilter( ldapGroupsTableName, "dn = {:dn}", dbx.Params{"dn": dn}, ) if err != nil { return nil, err } if errs := app.Dao().ExpandRecord(record, []string{"memberOf"}, nil); len(errs) > 0 { return nil, fmt.Errorf("ldap group - failed to expand: %v", errs) } return record, nil } // GetLdapUserByCN This function returns a record from the ldap users table by cn. It also expands the memberOf field. func GetLdapUserByCN(app *pocketbase.PocketBase, cn string) (*models.Record, error) { record, err := app.Dao().FindAuthRecordByUsername(ldapUsersTableName, cn) if err != nil { return nil, err } if errs := app.Dao().ExpandRecord(record, []string{"memberOf"}, nil); len(errs) > 0 { return nil, fmt.Errorf("ldap user - failed to expand: %v", errs) } return record, nil } // GetLdapGroupByObjectGUID This function returns a record from the ldap groups table by objectGUID. // It also expands the memberOf field. func GetLdapGroupByObjectGUID(app *pocketbase.PocketBase, objectGUID string) (*models.Record, error) { record, err := app.Dao().FindFirstRecordByFilter( ldapGroupsTableName, "objectGUID = {:objectGUID}", dbx.Params{"objectGUID": objectGUID}, ) if err != nil { return nil, err } if errs := app.Dao().ExpandRecord(record, []string{"memberOf"}, nil); len(errs) > 0 { return nil, fmt.Errorf("ldap group - failed to expand: %v", errs) } return record, nil } // GetLdapUserByObjectGUID This function returns a record from the ldap users table by objectGUID. // It also expands the memberOf field. func GetLdapUserByObjectGUID(app *pocketbase.PocketBase, objectGUID string) (*models.Record, error) { record, err := app.Dao().FindFirstRecordByFilter( ldapUsersTableName, "objectGUID = {:objectGUID}", dbx.Params{"objectGUID": objectGUID}, ) if err != nil { return nil, err } if errs := app.Dao().ExpandRecord(record, []string{"memberOf"}, nil); len(errs) > 0 { return nil, fmt.Errorf("ldap user - failed to expand: %v", errs) } return record, nil }