2021-11-05 09:37:30 +00:00
package memory
import (
2023-05-05 12:51:02 +00:00
"context"
2021-11-05 09:37:30 +00:00
"errors"
"fmt"
"strings"
2023-06-06 19:40:19 +00:00
"beryju.io/ldap"
2021-11-05 09:37:30 +00:00
"github.com/getsentry/sentry-go"
"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"
2023-06-08 13:16:40 +00:00
"goauthentik.io/internal/outpost/ldap/search/direct"
2021-11-05 09:37:30 +00:00
"goauthentik.io/internal/outpost/ldap/server"
2021-12-02 14:28:58 +00:00
"goauthentik.io/internal/outpost/ldap/utils"
2023-05-05 12:51:02 +00:00
"goauthentik.io/internal/outpost/ldap/utils/paginator"
2021-11-05 09:37:30 +00:00
)
type MemorySearcher struct {
si server . LDAPServerInstance
log * log . Entry
2023-06-08 13:16:40 +00:00
ds * direct . DirectSearcher
2021-11-05 09:37:30 +00:00
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" ) ,
2023-06-08 13:16:40 +00:00
ds : direct . NewDirectSearcher ( si ) ,
2021-11-05 09:37:30 +00:00
}
2023-01-17 10:12:22 +00:00
ms . log . Debug ( "initialised memory searcher" )
2023-05-05 12:51:02 +00:00
ms . users = paginator . FetchUsers ( ms . si . GetAPIClient ( ) . CoreApi . CoreUsersList ( context . TODO ( ) ) )
ms . groups = paginator . FetchGroups ( ms . si . GetAPIClient ( ) . CoreApi . CoreGroupsList ( context . TODO ( ) ) )
2021-11-05 09:37:30 +00:00
return ms
}
2023-06-08 13:16:40 +00:00
func ( ms * MemorySearcher ) SearchBase ( req * search . Request ) ( ldap . ServerSearchResult , error ) {
return ms . ds . SearchBase ( req )
}
func ( ms * MemorySearcher ) SearchSubschema ( req * search . Request ) ( ldap . ServerSearchResult , error ) {
return ms . ds . SearchSubschema ( req )
}
2021-11-05 09:37:30 +00:00
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
if len ( req . BindDN ) < 1 {
metrics . RequestsRejected . With ( prometheus . Labels {
"outpost_name" : ms . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "empty_bind_dn" ,
2022-05-29 19:45:25 +00:00
"app" : ms . si . GetAppSlug ( ) ,
2021-11-05 09:37:30 +00:00
} ) . 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" ,
2022-05-29 19:45:25 +00:00
"app" : ms . si . GetAppSlug ( ) ,
2021-11-05 09:37:30 +00:00
} ) . 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" ,
2022-05-29 19:45:25 +00:00
"app" : ms . si . GetAppSlug ( ) ,
2021-11-05 09:37:30 +00:00
} ) . 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
2023-06-08 13:16:40 +00:00
needUsers , needGroups := ms . si . GetNeededObjects ( scope , req . BaseDN , req . FilterObjectClass )
2021-12-02 14:28:58 +00:00
2022-01-25 10:27:27 +00:00
if scope >= 0 && strings . EqualFold ( req . BaseDN , baseDN ) {
2023-06-08 13:16:40 +00:00
if utils . IncludeObjectClass ( req . FilterObjectClass , constants . GetDomainOCs ( ) ) {
rootEntries , _ := ms . SearchBase ( req )
for _ , e := range rootEntries . Entries {
e . DN = ms . si . GetBaseDN ( )
entries = append ( entries , e )
}
2021-12-02 14:28:58 +00:00
}
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
2023-06-08 13:16:40 +00:00
var err error
2021-12-02 14:28:58 +00:00
if needUsers {
if flags . CanSearch {
users = & ms . users
} else {
2022-07-01 15:02:53 +00:00
u := make ( [ ] api . User , 1 )
2021-12-02 14:28:58 +00:00
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 " )
}
2022-07-01 15:02:53 +00:00
} else {
u [ 0 ] = * flags . UserInfo
2021-12-02 14:28:58 +00:00
}
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-08-05 22:52:12 +00:00
fg := api . NewGroup ( g . Pk , g . NumPk , g . Name , g . ParentName , [ ] api . GroupMember { u } )
fg . SetUsers ( [ ] int32 { flags . UserPk } )
if g . Parent . IsSet ( ) {
fg . SetParent ( * g . Parent . Get ( ) )
}
2022-05-26 13:15:30 +00:00
fg . SetAttributes ( g . Attributes )
2021-12-02 14:28:58 +00:00
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
2023-06-08 13:16:40 +00:00
if ! singleu && utils . IncludeObjectClass ( req . FilterObjectClass , constants . GetContainerOCs ( ) ) {
entries = append ( entries , utils . GetContainerEntry ( req . FilterObjectClass , ms . si . GetBaseUserDN ( ) , constants . OUUsers ) )
2021-12-02 14:28:58 +00:00
scope -= 1
}
2023-06-08 13:16:40 +00:00
if scope >= 0 && users != nil && utils . IncludeObjectClass ( req . FilterObjectClass , constants . GetUserOCs ( ) ) {
2021-12-02 14:28:58 +00:00
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
2023-06-08 13:16:40 +00:00
if ! singleg && utils . IncludeObjectClass ( req . FilterObjectClass , constants . GetContainerOCs ( ) ) {
entries = append ( entries , utils . GetContainerEntry ( req . FilterObjectClass , ms . si . GetBaseGroupDN ( ) , constants . OUGroups ) )
2021-12-02 14:28:58 +00:00
scope -= 1
2021-11-11 22:18:32 +00:00
}
2021-12-02 14:28:58 +00:00
2023-06-08 13:16:40 +00:00
if scope >= 0 && groups != nil && utils . IncludeObjectClass ( req . FilterObjectClass , constants . GetGroupOCs ( ) ) {
2021-12-02 14:28:58 +00:00
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
2023-06-08 13:16:40 +00:00
if ! singlevg && utils . IncludeObjectClass ( req . FilterObjectClass , constants . GetContainerOCs ( ) ) {
entries = append ( entries , utils . GetContainerEntry ( req . FilterObjectClass , ms . si . GetBaseVirtualGroupDN ( ) , constants . OUVirtualGroups ) )
2021-12-02 14:28:58 +00:00
scope -= 1
}
2023-06-08 13:16:40 +00:00
if scope >= 0 && users != nil && utils . IncludeObjectClass ( req . FilterObjectClass , constants . GetVirtualGroupOCs ( ) ) {
2021-12-02 14:28:58 +00:00
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
}