2021-11-05 09:37:30 +00:00
package memory
import (
"errors"
"fmt"
"strings"
"github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
2022-03-03 09:40:07 +00:00
"goauthentik.io/api/v3"
2021-11-05 09:37:30 +00:00
"goauthentik.io/internal/outpost/ldap/constants"
"goauthentik.io/internal/outpost/ldap/group"
"goauthentik.io/internal/outpost/ldap/metrics"
"goauthentik.io/internal/outpost/ldap/search"
"goauthentik.io/internal/outpost/ldap/server"
2021-12-02 14:28:58 +00:00
"goauthentik.io/internal/outpost/ldap/utils"
2021-11-05 09:37:30 +00:00
)
type MemorySearcher struct {
si server . LDAPServerInstance
log * log . Entry
users [ ] api . User
groups [ ] api . Group
}
func NewMemorySearcher ( si server . LDAPServerInstance ) * MemorySearcher {
ms := & MemorySearcher {
si : si ,
log : log . WithField ( "logger" , "authentik.outpost.ldap.searcher.memory" ) ,
}
ms . log . Info ( "initialised memory searcher" )
ms . users = ms . FetchUsers ( )
ms . groups = ms . FetchGroups ( )
return ms
}
func ( ms * MemorySearcher ) Search ( req * search . Request ) ( ldap . ServerSearchResult , error ) {
accsp := sentry . StartSpan ( req . Context ( ) , "authentik.providers.ldap.search.check_access" )
2022-01-25 10:27:27 +00:00
baseDN := ms . si . GetBaseDN ( )
2021-11-05 09:37:30 +00:00
2021-12-02 14:28:58 +00:00
filterOC , err := ldap . GetFilterObjectClass ( req . Filter )
2021-11-05 09:37:30 +00:00
if err != nil {
metrics . RequestsRejected . With ( prometheus . Labels {
"outpost_name" : ms . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "filter_parse_fail" ,
"dn" : req . BindDN ,
"client" : req . RemoteAddr ( ) ,
} ) . Inc ( )
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultOperationsError } , fmt . Errorf ( "Search Error: error parsing filter: %s" , req . Filter )
}
if len ( req . BindDN ) < 1 {
metrics . RequestsRejected . With ( prometheus . Labels {
"outpost_name" : ms . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "empty_bind_dn" ,
"dn" : req . BindDN ,
"client" : req . RemoteAddr ( ) ,
} ) . Inc ( )
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultInsufficientAccessRights } , fmt . Errorf ( "Search Error: Anonymous BindDN not allowed %s" , req . BindDN )
}
2022-01-25 10:27:27 +00:00
if ! utils . HasSuffixNoCase ( req . BindDN , "," + baseDN ) {
2021-11-05 09:37:30 +00:00
metrics . RequestsRejected . With ( prometheus . Labels {
"outpost_name" : ms . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "invalid_bind_dn" ,
"dn" : req . BindDN ,
"client" : req . RemoteAddr ( ) ,
} ) . Inc ( )
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultInsufficientAccessRights } , fmt . Errorf ( "Search Error: BindDN %s not in our BaseDN %s" , req . BindDN , ms . si . GetBaseDN ( ) )
}
2022-05-21 13:48:50 +00:00
flags := ms . si . GetFlags ( req . BindDN )
if flags == nil {
2021-11-05 09:37:30 +00:00
req . Log ( ) . Debug ( "User info not cached" )
metrics . RequestsRejected . With ( prometheus . Labels {
"outpost_name" : ms . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "user_info_not_cached" ,
"dn" : req . BindDN ,
"client" : req . RemoteAddr ( ) ,
} ) . Inc ( )
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultInsufficientAccessRights } , errors . New ( "access denied" )
}
2021-12-02 14:28:58 +00:00
accsp . Finish ( )
entries := make ( [ ] * ldap . Entry , 0 )
2021-11-05 09:37:30 +00:00
2021-12-02 14:28:58 +00:00
scope := req . SearchRequest . Scope
needUsers , needGroups := ms . si . GetNeededObjects ( scope , req . BaseDN , filterOC )
2022-01-25 10:27:27 +00:00
if scope >= 0 && strings . EqualFold ( req . BaseDN , baseDN ) {
2021-12-02 14:28:58 +00:00
if utils . IncludeObjectClass ( filterOC , constants . GetDomainOCs ( ) ) {
entries = append ( entries , ms . si . GetBaseEntry ( ) )
}
scope -= 1 // Bring it from WholeSubtree to SingleLevel and so on
2021-11-05 09:37:30 +00:00
}
2021-12-02 14:28:58 +00:00
var users * [ ] api . User
var groups [ ] * group . LDAPGroup
if needUsers {
if flags . CanSearch {
users = & ms . users
} else {
if flags . UserInfo == nil {
for i , u := range ms . users {
if u . Pk == flags . UserPk {
flags . UserInfo = & ms . users [ i ]
}
}
if flags . UserInfo == nil {
req . Log ( ) . WithField ( "pk" , flags . UserPk ) . Warning ( "User with pk is not in local cache" )
err = fmt . Errorf ( "failed to get userinfo" )
}
}
u := make ( [ ] api . User , 1 )
u [ 0 ] = * flags . UserInfo
users = & u
}
2021-11-05 09:37:30 +00:00
}
2021-12-02 14:28:58 +00:00
if needGroups {
groups = make ( [ ] * group . LDAPGroup , 0 )
2021-11-11 22:18:32 +00:00
for _ , g := range ms . groups {
2021-12-02 14:28:58 +00:00
if flags . CanSearch {
groups = append ( groups , group . FromAPIGroup ( g , ms . si ) )
} else {
// If the user cannot search, we're going to only return
// the groups they're in _and_ only return themselves
// as a member.
for _ , u := range g . UsersObj {
if flags . UserPk == u . Pk {
2022-01-07 08:51:41 +00:00
//TODO: Is there a better way to clone this object?
2022-03-22 22:49:54 +00:00
fg := api . NewGroup ( g . Pk , g . NumPk , g . Name , g . Parent , g . ParentName , [ ] int32 { flags . UserPk } , [ ] api . GroupMember { u } )
2021-12-02 14:28:58 +00:00
fg . SetAttributes ( * g . Attributes )
fg . SetIsSuperuser ( * g . IsSuperuser )
groups = append ( groups , group . FromAPIGroup ( * fg , ms . si ) )
break
}
}
}
}
}
if err != nil {
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultOperationsError } , err
}
2022-01-25 10:27:27 +00:00
if scope >= 0 && ( strings . EqualFold ( req . BaseDN , ms . si . GetBaseDN ( ) ) || utils . HasSuffixNoCase ( req . BaseDN , ms . si . GetBaseUserDN ( ) ) ) {
singleu := utils . HasSuffixNoCase ( req . BaseDN , "," + ms . si . GetBaseUserDN ( ) )
2021-12-02 14:28:58 +00:00
if ! singleu && utils . IncludeObjectClass ( filterOC , constants . GetContainerOCs ( ) ) {
entries = append ( entries , utils . GetContainerEntry ( filterOC , ms . si . GetBaseUserDN ( ) , constants . OUUsers ) )
scope -= 1
}
if scope >= 0 && users != nil && utils . IncludeObjectClass ( filterOC , constants . GetUserOCs ( ) ) {
for _ , u := range * users {
entry := ms . si . UserEntry ( u )
2022-01-25 10:27:27 +00:00
if strings . EqualFold ( req . BaseDN , entry . DN ) || ! singleu {
2021-12-02 14:28:58 +00:00
entries = append ( entries , entry )
}
}
}
scope += 1 // Return the scope to what it was before we descended
}
2022-01-25 10:27:27 +00:00
if scope >= 0 && ( strings . EqualFold ( req . BaseDN , ms . si . GetBaseDN ( ) ) || utils . HasSuffixNoCase ( req . BaseDN , ms . si . GetBaseGroupDN ( ) ) ) {
singleg := utils . HasSuffixNoCase ( req . BaseDN , "," + ms . si . GetBaseGroupDN ( ) )
2021-12-02 14:28:58 +00:00
if ! singleg && utils . IncludeObjectClass ( filterOC , constants . GetContainerOCs ( ) ) {
entries = append ( entries , utils . GetContainerEntry ( filterOC , ms . si . GetBaseGroupDN ( ) , constants . OUGroups ) )
scope -= 1
2021-11-11 22:18:32 +00:00
}
2021-12-02 14:28:58 +00:00
if scope >= 0 && groups != nil && utils . IncludeObjectClass ( filterOC , constants . GetGroupOCs ( ) ) {
for _ , g := range groups {
2022-01-25 10:27:27 +00:00
if strings . EqualFold ( req . BaseDN , g . DN ) || ! singleg {
2021-12-02 14:28:58 +00:00
entries = append ( entries , g . Entry ( ) )
}
}
2021-11-11 22:18:32 +00:00
}
2021-12-02 14:28:58 +00:00
scope += 1 // Return the scope to what it was before we descended
}
2022-01-25 10:27:27 +00:00
if scope >= 0 && ( strings . EqualFold ( req . BaseDN , ms . si . GetBaseDN ( ) ) || utils . HasSuffixNoCase ( req . BaseDN , ms . si . GetBaseVirtualGroupDN ( ) ) ) {
singlevg := utils . HasSuffixNoCase ( req . BaseDN , "," + ms . si . GetBaseVirtualGroupDN ( ) )
2021-12-02 14:28:58 +00:00
if ! singlevg && utils . IncludeObjectClass ( filterOC , constants . GetContainerOCs ( ) ) {
entries = append ( entries , utils . GetContainerEntry ( filterOC , ms . si . GetBaseVirtualGroupDN ( ) , constants . OUVirtualGroups ) )
scope -= 1
}
if scope >= 0 && users != nil && utils . IncludeObjectClass ( filterOC , constants . GetVirtualGroupOCs ( ) ) {
for _ , u := range * users {
entry := group . FromAPIUser ( u , ms . si ) . Entry ( )
2022-01-25 10:27:27 +00:00
if strings . EqualFold ( req . BaseDN , entry . DN ) || ! singlevg {
2021-12-02 14:28:58 +00:00
entries = append ( entries , entry )
}
}
2021-11-05 09:37:30 +00:00
}
}
2021-12-02 14:28:58 +00:00
2021-11-05 09:37:30 +00:00
return ldap . ServerSearchResult { Entries : entries , Referrals : [ ] string { } , Controls : [ ] ldap . Control { } , ResultCode : ldap . LDAPResultSuccess } , nil
}