diff --git a/outpost/pkg/ldap/instance_bind.go b/outpost/pkg/ldap/instance_bind.go index c587b948c..f09b8dc30 100644 --- a/outpost/pkg/ldap/instance_bind.go +++ b/outpost/pkg/ldap/instance_bind.go @@ -53,8 +53,6 @@ func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn ne // Create new http client that also sets the correct ip config := api.NewConfiguration() - // Carry over the bearer authentication, so that failed login attempts are attributed to the outpost - config.DefaultHeader = pi.s.ac.Client.GetConfig().DefaultHeader config.Host = pi.s.ac.Client.GetConfig().Host config.Scheme = pi.s.ac.Client.GetConfig().Scheme config.HTTPClient = &http.Client{ @@ -76,7 +74,7 @@ func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn ne if !passed { return ldap.LDAPResultInvalidCredentials, nil } - r, err := pi.s.ac.Client.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), pi.appSlug).Execute() + r, err := apiClient.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), pi.appSlug).Execute() if r.StatusCode == 403 { pi.log.WithField("bindDN", bindDN).Info("Access denied for user") return ldap.LDAPResultInsufficientAccessRights, nil @@ -87,7 +85,7 @@ func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn ne } pi.log.WithField("bindDN", bindDN).Info("User has access") // Get user info to store in context - userInfo, _, err := pi.s.ac.Client.CoreApi.CoreUsersMeRetrieve(context.Background()).Execute() + userInfo, _, err := apiClient.CoreApi.CoreUsersMeRetrieve(context.Background()).Execute() if err != nil { pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to get user info") return ldap.LDAPResultOperationsError, nil diff --git a/outpost/pkg/ldap/instance_search.go b/outpost/pkg/ldap/instance_search.go index f5a4462c7..c385068a0 100644 --- a/outpost/pkg/ldap/instance_search.go +++ b/outpost/pkg/ldap/instance_search.go @@ -8,8 +8,15 @@ import ( "strings" "github.com/nmcclain/ldap" + "goauthentik.io/outpost/api" ) +func (pi *ProviderInstance) SearchMe(user api.User, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) { + entries := make([]*ldap.Entry, 1) + entries[0] = pi.UserEntry(user) + return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil +} + func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) { bindDN = strings.ToLower(bindDN) baseDN := strings.ToLower("," + pi.BaseDN) @@ -29,14 +36,13 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest, pi.boundUsersMutex.RLock() defer pi.boundUsersMutex.RUnlock() flags, ok := pi.boundUsers[bindDN] - pi.log.WithField("bindDN", bindDN).WithField("ok", ok).Debugf("%+v\n", flags) if !ok { pi.log.Debug("User info not cached") return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied") } if !flags.CanSearch { - pi.log.Debug("User can't search") - return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied") + pi.log.Debug("User can't search, showing info about user") + return pi.SearchMe(flags.UserInfo, searchReq, conn) } switch filterEntity { @@ -49,24 +55,7 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest, } pi.log.WithField("count", len(groups.Results)).Trace("Got results from API") for _, g := range groups.Results { - attrs := []*ldap.EntryAttribute{ - { - Name: "cn", - Values: []string{g.Name}, - }, - { - Name: "uid", - Values: []string{string(g.Pk)}, - }, - { - Name: "objectClass", - Values: []string{GroupObjectClass, "goauthentik.io/ldap/group"}, - }, - } - attrs = append(attrs, AKAttrsToLDAP(g.Attributes)...) - - dn := pi.GetGroupDN(g) - entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs}) + entries = append(entries, pi.GroupEntry(g)) } case UserObjectClass, "": users, _, err := pi.s.ac.Client.CoreApi.CoreUsersList(context.Background()).Execute() @@ -74,53 +63,79 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest, return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err) } for _, u := range users.Results { - attrs := []*ldap.EntryAttribute{ - { - Name: "cn", - Values: []string{u.Username}, - }, - { - Name: "uid", - Values: []string{u.Uid}, - }, - { - Name: "name", - Values: []string{u.Name}, - }, - { - Name: "displayName", - Values: []string{u.Name}, - }, - { - Name: "mail", - Values: []string{*u.Email}, - }, - { - Name: "objectClass", - Values: []string{UserObjectClass, "organizationalPerson", "goauthentik.io/ldap/user"}, - }, - } - - if *u.IsActive { - attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}}) - } else { - attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}}) - } - - if u.IsSuperuser { - attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"inactive"}}) - } else { - attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"active"}}) - } - - attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: pi.GroupsForUser(u)}) - - attrs = append(attrs, AKAttrsToLDAP(u.Attributes)...) - - dn := fmt.Sprintf("cn=%s,%s", u.Username, pi.UserDN) - entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs}) + entries = append(entries, pi.UserEntry(u)) } } pi.log.WithField("filter", searchReq.Filter).Debug("Search OK") return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil } + +func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry { + attrs := []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{u.Username}, + }, + { + Name: "uid", + Values: []string{u.Uid}, + }, + { + Name: "name", + Values: []string{u.Name}, + }, + { + Name: "displayName", + Values: []string{u.Name}, + }, + { + Name: "mail", + Values: []string{*u.Email}, + }, + { + Name: "objectClass", + Values: []string{UserObjectClass, "organizationalPerson", "goauthentik.io/ldap/user"}, + }, + } + + if *u.IsActive { + attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}}) + } else { + attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}}) + } + + if u.IsSuperuser { + attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"inactive"}}) + } else { + attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"active"}}) + } + + attrs = append(attrs, &ldap.EntryAttribute{Name: "memberOf", Values: pi.GroupsForUser(u)}) + + attrs = append(attrs, AKAttrsToLDAP(u.Attributes)...) + + dn := fmt.Sprintf("cn=%s,%s", u.Username, pi.UserDN) + + return &ldap.Entry{DN: dn, Attributes: attrs} +} + +func (pi *ProviderInstance) GroupEntry(g api.Group) *ldap.Entry { + attrs := []*ldap.EntryAttribute{ + { + Name: "cn", + Values: []string{g.Name}, + }, + { + Name: "uid", + Values: []string{string(g.Pk)}, + }, + { + Name: "objectClass", + Values: []string{GroupObjectClass, "goauthentik.io/ldap/group"}, + }, + } + attrs = append(attrs, AKAttrsToLDAP(g.Attributes)...) + + dn := pi.GetGroupDN(g) + return &ldap.Entry{DN: dn, Attributes: attrs} +} diff --git a/outpost/pkg/ldap/utils.go b/outpost/pkg/ldap/utils.go index 4c8dc5704..b32c20783 100644 --- a/outpost/pkg/ldap/utils.go +++ b/outpost/pkg/ldap/utils.go @@ -9,7 +9,8 @@ import ( func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute { attrList := []*ldap.EntryAttribute{} - for attrKey, attrValue := range attrs.(map[string]interface{}) { + a := attrs.(*map[string]interface{}) + for attrKey, attrValue := range *a { entry := &ldap.EntryAttribute{Name: attrKey} switch t := attrValue.(type) { case []string: