outposts/ldap: return user info when user can't search
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
763c3fcfe0
commit
2a5926608f
|
@ -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