2021-04-26 09:53:06 +00:00
package ldap
import (
2021-05-16 19:07:01 +00:00
"context"
2021-05-04 19:49:15 +00:00
"errors"
2021-04-26 09:53:06 +00:00
"fmt"
"net"
"strings"
2021-05-04 19:49:15 +00:00
"github.com/nmcclain/ldap"
2021-05-24 14:09:05 +00:00
"goauthentik.io/outpost/api"
2021-04-26 09:53:06 +00:00
)
2021-05-24 14:09:05 +00:00
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
}
2021-04-26 09:53:06 +00:00
func ( pi * ProviderInstance ) Search ( bindDN string , searchReq ldap . SearchRequest , conn net . Conn ) ( ldap . ServerSearchResult , error ) {
bindDN = strings . ToLower ( bindDN )
baseDN := strings . ToLower ( "," + pi . BaseDN )
entries := [ ] * ldap . Entry { }
filterEntity , err := ldap . GetFilterObjectClass ( searchReq . Filter )
if err != nil {
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultOperationsError } , fmt . Errorf ( "Search Error: error parsing filter: %s" , searchReq . Filter )
}
if len ( bindDN ) < 1 {
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultInsufficientAccessRights } , fmt . Errorf ( "Search Error: Anonymous BindDN not allowed %s" , bindDN )
}
if ! strings . HasSuffix ( bindDN , baseDN ) {
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultInsufficientAccessRights } , fmt . Errorf ( "Search Error: BindDN %s not in our BaseDN %s" , bindDN , pi . BaseDN )
}
2021-05-04 19:49:15 +00:00
pi . boundUsersMutex . RLock ( )
defer pi . boundUsersMutex . RUnlock ( )
flags , ok := pi . boundUsers [ bindDN ]
if ! ok {
2021-05-12 16:49:15 +00:00
pi . log . Debug ( "User info not cached" )
2021-05-08 18:59:31 +00:00
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultInsufficientAccessRights } , errors . New ( "access denied" )
2021-05-04 19:49:15 +00:00
}
if ! flags . CanSearch {
2021-05-24 14:09:05 +00:00
pi . log . Debug ( "User can't search, showing info about user" )
return pi . SearchMe ( flags . UserInfo , searchReq , conn )
2021-05-04 19:49:15 +00:00
}
2021-04-26 09:53:06 +00:00
switch filterEntity {
default :
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultOperationsError } , fmt . Errorf ( "Search Error: unhandled filter type: %s [%s]" , filterEntity , searchReq . Filter )
case GroupObjectClass :
2021-05-16 19:35:23 +00:00
groups , _ , err := pi . s . ac . Client . CoreApi . CoreGroupsList ( context . Background ( ) ) . Execute ( )
2021-04-26 09:53:06 +00:00
if err != nil {
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultOperationsError } , fmt . Errorf ( "API Error: %s" , err )
}
2021-05-16 19:07:01 +00:00
pi . log . WithField ( "count" , len ( groups . Results ) ) . Trace ( "Got results from API" )
for _ , g := range groups . Results {
2021-05-24 14:09:05 +00:00
entries = append ( entries , pi . GroupEntry ( g ) )
2021-04-26 09:53:06 +00:00
}
case UserObjectClass , "" :
2021-05-16 19:35:23 +00:00
users , _ , err := pi . s . ac . Client . CoreApi . CoreUsersList ( context . Background ( ) ) . Execute ( )
2021-04-26 09:53:06 +00:00
if err != nil {
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultOperationsError } , fmt . Errorf ( "API Error: %s" , err )
}
2021-05-16 19:07:01 +00:00
for _ , u := range users . Results {
2021-05-24 14:09:05 +00:00
entries = append ( entries , pi . UserEntry ( u ) )
2021-04-26 09:53:06 +00:00
}
}
pi . log . WithField ( "filter" , searchReq . Filter ) . Debug ( "Search OK" )
return ldap . ServerSearchResult { Entries : entries , Referrals : [ ] string { } , Controls : [ ] ldap . Control { } , ResultCode : ldap . LDAPResultSuccess } , nil
}
2021-05-24 14:09:05 +00:00
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 }
}