2021-11-05 09:37:30 +00:00
package direct
import (
"errors"
"fmt"
"strings"
log "github.com/sirupsen/logrus"
2021-12-02 14:28:58 +00:00
"golang.org/x/sync/errgroup"
2021-11-05 09:37:30 +00:00
"github.com/getsentry/sentry-go"
"github.com/nmcclain/ldap"
"github.com/prometheus/client_golang/prometheus"
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"
"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 DirectSearcher struct {
si server . LDAPServerInstance
log * l og . Entry
}
func NewDirectSearcher ( si server . LDAPServerInstance ) * DirectSearcher {
ds := & DirectSearcher {
si : si ,
log : log . WithField ( "logger" , "authentik.outpost.ldap.searcher.direct" ) ,
}
ds . log . Info ( "initialised direct searcher" )
return ds
}
func ( ds * DirectSearcher ) 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 := ds . 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" : ds . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "filter_parse_fail" ,
2022-05-29 19:45:25 +00:00
"app" : ds . si . GetAppSlug ( ) ,
2021-11-05 09:37:30 +00:00
} ) . 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" : ds . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "empty_bind_dn" ,
2022-05-29 19:45:25 +00:00
"app" : ds . 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" : ds . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "invalid_bind_dn" ,
2022-05-29 19:45:25 +00:00
"app" : ds . 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 , ds . si . GetBaseDN ( ) )
}
2022-05-21 13:48:50 +00:00
flags := ds . 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" : ds . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "user_info_not_cached" ,
2022-05-29 19:45:25 +00:00
"app" : ds . si . GetAppSlug ( ) ,
2021-11-05 09:37:30 +00:00
} ) . Inc ( )
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultInsufficientAccessRights } , errors . New ( "access denied" )
}
accsp . Finish ( )
parsedFilter , err := ldap . CompileFilter ( req . Filter )
if err != nil {
metrics . RequestsRejected . With ( prometheus . Labels {
"outpost_name" : ds . si . GetOutpostName ( ) ,
"type" : "search" ,
"reason" : "filter_parse_fail" ,
2022-05-29 19:45:25 +00:00
"app" : ds . si . GetAppSlug ( ) ,
2021-11-05 09:37:30 +00:00
} ) . Inc ( )
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultOperationsError } , fmt . Errorf ( "Search Error: error parsing filter: %s" , req . Filter )
}
2021-12-02 14:28:58 +00:00
entries := make ( [ ] * ldap . Entry , 0 )
2021-11-05 09:37:30 +00:00
// Create a custom client to set additional headers
c := api . NewAPIClient ( ds . si . GetAPIClient ( ) . GetConfig ( ) )
c . GetConfig ( ) . AddDefaultHeader ( "X-authentik-outpost-ldap-query" , req . Filter )
2021-12-02 14:28:58 +00:00
scope := req . SearchRequest . Scope
needUsers , needGroups := ds . 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 , ds . si . GetBaseEntry ( ) )
}
scope -= 1 // Bring it from WholeSubtree to SingleLevel and so on
}
var users * [ ] api . User
var groups * [ ] api . Group
2022-03-19 17:28:27 +00:00
errs , errCtx := errgroup . WithContext ( req . Context ( ) )
2021-12-02 14:28:58 +00:00
if needUsers {
errs . Go ( func ( ) error {
if flags . CanSearch {
2022-03-19 17:28:27 +00:00
uapisp := sentry . StartSpan ( errCtx , "authentik.providers.ldap.search.api_user" )
2021-12-02 14:28:58 +00:00
searchReq , skip := utils . ParseFilterForUser ( c . CoreApi . CoreUsersList ( uapisp . Context ( ) ) , parsedFilter , false )
if skip {
req . Log ( ) . Trace ( "Skip backend request" )
return nil
}
2023-05-05 12:51:02 +00:00
u := paginator . FetchUsers ( searchReq )
2021-12-02 14:28:58 +00:00
uapisp . Finish ( )
2023-05-05 12:51:02 +00:00
users = & u
2021-12-02 14:28:58 +00:00
} else {
if flags . UserInfo == nil {
2022-03-19 17:28:27 +00:00
uapisp := sentry . StartSpan ( errCtx , "authentik.providers.ldap.search.api_user" )
u , _ , err := c . CoreApi . CoreUsersRetrieve ( uapisp . Context ( ) , flags . UserPk ) . Execute ( )
2021-12-02 14:28:58 +00:00
uapisp . Finish ( )
if err != nil {
req . Log ( ) . WithError ( err ) . Warning ( "Failed to get user info" )
return fmt . Errorf ( "failed to get userinfo" )
}
2022-05-26 13:15:30 +00:00
flags . UserInfo = u
2021-12-02 14:28:58 +00:00
}
u := make ( [ ] api . User , 1 )
u [ 0 ] = * flags . UserInfo
users = & u
}
return nil
} )
}
if needGroups {
errs . Go ( func ( ) error {
2022-03-19 17:28:27 +00:00
gapisp := sentry . StartSpan ( errCtx , "authentik.providers.ldap.search.api_group" )
2021-11-05 09:37:30 +00:00
searchReq , skip := utils . ParseFilterForGroup ( c . CoreApi . CoreGroupsList ( gapisp . Context ( ) ) , parsedFilter , false )
if skip {
req . Log ( ) . Trace ( "Skip backend request" )
2021-12-02 14:28:58 +00:00
return nil
}
if ! flags . CanSearch {
// If they can't search, filter all groups by those they're a member of
searchReq = searchReq . MembersByPk ( [ ] int32 { flags . UserPk } )
2021-11-05 09:37:30 +00:00
}
2021-12-02 14:28:58 +00:00
2023-05-05 12:51:02 +00:00
g := paginator . FetchGroups ( searchReq )
2021-11-05 09:37:30 +00:00
gapisp . Finish ( )
2023-05-05 12:51:02 +00:00
req . Log ( ) . WithField ( "count" , len ( g ) ) . Trace ( "Got results from API" )
2021-11-05 09:37:30 +00:00
2021-12-02 14:28:58 +00:00
if ! flags . CanSearch {
2023-05-05 12:51:02 +00:00
for i , results := range g {
2021-12-02 14:28:58 +00:00
// If they can't search, remove any users from the group results except the one we're looking for.
2023-05-05 12:51:02 +00:00
g [ i ] . Users = [ ] int32 { flags . UserPk }
2021-12-02 14:28:58 +00:00
for _ , u := range results . UsersObj {
if u . Pk == flags . UserPk {
2023-05-05 12:51:02 +00:00
g [ i ] . UsersObj = [ ] api . GroupMember { u }
2021-12-02 14:28:58 +00:00
break
}
}
}
2021-11-05 09:37:30 +00:00
}
2023-05-05 12:51:02 +00:00
groups = & g
2021-12-02 14:28:58 +00:00
return nil
} )
}
err = errs . Wait ( )
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 , ds . si . GetBaseDN ( ) ) || utils . HasSuffixNoCase ( req . BaseDN , ds . si . GetBaseUserDN ( ) ) ) {
singleu := utils . HasSuffixNoCase ( req . BaseDN , "," + ds . si . GetBaseUserDN ( ) )
2021-12-02 14:28:58 +00:00
if ! singleu && utils . IncludeObjectClass ( filterOC , constants . GetContainerOCs ( ) ) {
entries = append ( entries , utils . GetContainerEntry ( filterOC , ds . si . GetBaseUserDN ( ) , constants . OUUsers ) )
scope -= 1
}
if scope >= 0 && users != nil && utils . IncludeObjectClass ( filterOC , constants . GetUserOCs ( ) ) {
for _ , u := range * users {
entry := ds . 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 )
}
2021-11-05 09:37:30 +00:00
}
2021-12-02 14:28:58 +00:00
}
2021-11-05 09:37:30 +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 , ds . si . GetBaseDN ( ) ) || utils . HasSuffixNoCase ( req . BaseDN , ds . si . GetBaseGroupDN ( ) ) ) {
singleg := utils . HasSuffixNoCase ( req . BaseDN , "," + ds . si . GetBaseGroupDN ( ) )
2021-12-02 14:28:58 +00:00
if ! singleg && utils . IncludeObjectClass ( filterOC , constants . GetContainerOCs ( ) ) {
entries = append ( entries , utils . GetContainerEntry ( filterOC , ds . si . GetBaseGroupDN ( ) , constants . OUGroups ) )
scope -= 1
}
if scope >= 0 && groups != nil && utils . IncludeObjectClass ( filterOC , constants . GetGroupOCs ( ) ) {
for _ , g := range * groups {
entry := group . FromAPIGroup ( g , ds . si ) . Entry ( )
2022-01-25 10:27:27 +00:00
if strings . EqualFold ( req . BaseDN , entry . DN ) || ! singleg {
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
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 , ds . si . GetBaseDN ( ) ) || utils . HasSuffixNoCase ( req . BaseDN , ds . si . GetBaseVirtualGroupDN ( ) ) ) {
singlevg := utils . HasSuffixNoCase ( req . BaseDN , "," + ds . si . GetBaseVirtualGroupDN ( ) )
2021-12-02 14:28:58 +00:00
2022-03-19 17:26:26 +00:00
if ! singlevg && utils . IncludeObjectClass ( filterOC , constants . GetContainerOCs ( ) ) {
2021-12-02 14:28:58 +00:00
entries = append ( entries , utils . GetContainerEntry ( filterOC , ds . si . GetBaseVirtualGroupDN ( ) , constants . OUVirtualGroups ) )
scope -= 1
2021-11-05 09:37:30 +00:00
}
2021-12-02 14:28:58 +00:00
if scope >= 0 && users != nil && utils . IncludeObjectClass ( filterOC , constants . GetVirtualGroupOCs ( ) ) {
for _ , u := range * users {
entry := group . FromAPIUser ( u , ds . 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
}