outposts/ldap: return user info when user can't search
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		| @ -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 | 	// Create new http client that also sets the correct ip | ||||||
| 	config := api.NewConfiguration() | 	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.Host = pi.s.ac.Client.GetConfig().Host | ||||||
| 	config.Scheme = pi.s.ac.Client.GetConfig().Scheme | 	config.Scheme = pi.s.ac.Client.GetConfig().Scheme | ||||||
| 	config.HTTPClient = &http.Client{ | 	config.HTTPClient = &http.Client{ | ||||||
| @ -76,7 +74,7 @@ func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn ne | |||||||
| 	if !passed { | 	if !passed { | ||||||
| 		return ldap.LDAPResultInvalidCredentials, nil | 		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 { | 	if r.StatusCode == 403 { | ||||||
| 		pi.log.WithField("bindDN", bindDN).Info("Access denied for user") | 		pi.log.WithField("bindDN", bindDN).Info("Access denied for user") | ||||||
| 		return ldap.LDAPResultInsufficientAccessRights, nil | 		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") | 	pi.log.WithField("bindDN", bindDN).Info("User has access") | ||||||
| 	// Get user info to store in context | 	// 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 { | 	if err != nil { | ||||||
| 		pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to get user info") | 		pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to get user info") | ||||||
| 		return ldap.LDAPResultOperationsError, nil | 		return ldap.LDAPResultOperationsError, nil | ||||||
|  | |||||||
| @ -8,8 +8,15 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/nmcclain/ldap" | 	"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) { | func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) { | ||||||
| 	bindDN = strings.ToLower(bindDN) | 	bindDN = strings.ToLower(bindDN) | ||||||
| 	baseDN := strings.ToLower("," + pi.BaseDN) | 	baseDN := strings.ToLower("," + pi.BaseDN) | ||||||
| @ -29,14 +36,13 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest, | |||||||
| 	pi.boundUsersMutex.RLock() | 	pi.boundUsersMutex.RLock() | ||||||
| 	defer pi.boundUsersMutex.RUnlock() | 	defer pi.boundUsersMutex.RUnlock() | ||||||
| 	flags, ok := pi.boundUsers[bindDN] | 	flags, ok := pi.boundUsers[bindDN] | ||||||
| 	pi.log.WithField("bindDN", bindDN).WithField("ok", ok).Debugf("%+v\n", flags) |  | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		pi.log.Debug("User info not cached") | 		pi.log.Debug("User info not cached") | ||||||
| 		return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied") | 		return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied") | ||||||
| 	} | 	} | ||||||
| 	if !flags.CanSearch { | 	if !flags.CanSearch { | ||||||
| 		pi.log.Debug("User can't search") | 		pi.log.Debug("User can't search, showing info about user") | ||||||
| 		return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied") | 		return pi.SearchMe(flags.UserInfo, searchReq, conn) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch filterEntity { | 	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") | 		pi.log.WithField("count", len(groups.Results)).Trace("Got results from API") | ||||||
| 		for _, g := range groups.Results { | 		for _, g := range groups.Results { | ||||||
| 			attrs := []*ldap.EntryAttribute{ | 			entries = append(entries, pi.GroupEntry(g)) | ||||||
| 				{ |  | ||||||
| 					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}) |  | ||||||
| 		} | 		} | ||||||
| 	case UserObjectClass, "": | 	case UserObjectClass, "": | ||||||
| 		users, _, err := pi.s.ac.Client.CoreApi.CoreUsersList(context.Background()).Execute() | 		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) | 			return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err) | ||||||
| 		} | 		} | ||||||
| 		for _, u := range users.Results { | 		for _, u := range users.Results { | ||||||
| 			attrs := []*ldap.EntryAttribute{ | 			entries = append(entries, pi.UserEntry(u)) | ||||||
| 				{ |  | ||||||
| 					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}) |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	pi.log.WithField("filter", searchReq.Filter).Debug("Search OK") | 	pi.log.WithField("filter", searchReq.Filter).Debug("Search OK") | ||||||
| 	return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil | 	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} | ||||||
|  | } | ||||||
|  | |||||||
| @ -9,7 +9,8 @@ import ( | |||||||
|  |  | ||||||
| func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute { | func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute { | ||||||
| 	attrList := []*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} | 		entry := &ldap.EntryAttribute{Name: attrKey} | ||||||
| 		switch t := attrValue.(type) { | 		switch t := attrValue.(type) { | ||||||
| 		case []string: | 		case []string: | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer