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" )
2021-07-14 07:17:01 +00:00
2021-05-16 19:07:01 +00:00
for _ , g := range groups . Results {
2021-07-14 07:17:01 +00:00
entries = append ( entries , pi . GroupEntry ( pi . APIGroupToLDAPGroup ( g ) ) )
}
users , _ , err := pi . s . ac . Client . CoreApi . CoreUsersList ( context . Background ( ) ) . Execute ( )
if err != nil {
return ldap . ServerSearchResult { ResultCode : ldap . LDAPResultOperationsError } , fmt . Errorf ( "API Error: %s" , err )
}
for _ , u := range users . Results {
entries = append ( entries , pi . GroupEntry ( pi . APIUserToLDAPGroup ( u ) ) )
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" } ,
} ,
2021-07-14 07:17:01 +00:00
{
Name : "uidNumber" ,
2021-07-14 18:37:27 +00:00
Values : [ ] string { pi . GetUidNumber ( u ) } ,
2021-07-14 07:17:01 +00:00
} ,
{
Name : "gidNumber" ,
2021-07-14 18:37:27 +00:00
Values : [ ] string { pi . GetUidNumber ( u ) } ,
2021-07-14 07:17:01 +00:00
} ,
2021-05-24 14:09:05 +00:00
}
attrs = append ( attrs , & ldap . EntryAttribute { Name : "memberOf" , Values : pi . GroupsForUser ( u ) } )
2021-07-04 16:10:39 +00:00
// Old fields for backwards compatibility
attrs = append ( attrs , & ldap . EntryAttribute { Name : "accountStatus" , Values : [ ] string { BoolToString ( * u . IsActive ) } } )
attrs = append ( attrs , & ldap . EntryAttribute { Name : "superuser" , Values : [ ] string { BoolToString ( u . IsSuperuser ) } } )
2021-07-01 09:50:51 +00:00
attrs = append ( attrs , & ldap . EntryAttribute { Name : "goauthentik.io/ldap/active" , Values : [ ] string { BoolToString ( * u . IsActive ) } } )
attrs = append ( attrs , & ldap . EntryAttribute { Name : "goauthentik.io/ldap/superuser" , Values : [ ] string { BoolToString ( u . IsSuperuser ) } } )
2021-05-24 14:09:05 +00:00
attrs = append ( attrs , AKAttrsToLDAP ( u . Attributes ) ... )
2021-07-13 16:24:18 +00:00
dn := pi . GetUserDN ( u . Username )
2021-05-24 14:09:05 +00:00
return & ldap . Entry { DN : dn , Attributes : attrs }
}
2021-07-14 07:17:01 +00:00
func ( pi * ProviderInstance ) GroupEntry ( g LDAPGroup ) * ldap . Entry {
2021-05-24 14:09:05 +00:00
attrs := [ ] * ldap . EntryAttribute {
{
Name : "cn" ,
2021-07-14 07:17:01 +00:00
Values : [ ] string { g . cn } ,
2021-05-24 14:09:05 +00:00
} ,
{
Name : "uid" ,
2021-07-14 07:17:01 +00:00
Values : [ ] string { g . uid } ,
2021-05-24 14:09:05 +00:00
} ,
{
2021-07-14 07:17:01 +00:00
Name : "gidNumber" ,
2021-07-14 18:37:27 +00:00
Values : [ ] string { g . gidNumber } ,
2021-05-24 14:09:05 +00:00
} ,
}
2021-07-13 16:24:18 +00:00
2021-07-14 18:37:27 +00:00
if g . isVirtualGroup {
2021-07-14 07:17:01 +00:00
attrs = append ( attrs , & ldap . EntryAttribute {
2021-07-14 18:37:27 +00:00
Name : "objectClass" ,
2021-07-14 07:17:01 +00:00
Values : [ ] string { GroupObjectClass , "goauthentik.io/ldap/group" , "goauthentik.io/ldap/virtual-group" } ,
} )
} else {
attrs = append ( attrs , & ldap . EntryAttribute {
2021-07-14 18:37:27 +00:00
Name : "objectClass" ,
2021-07-14 07:17:01 +00:00
Values : [ ] string { GroupObjectClass , "goauthentik.io/ldap/group" } ,
} )
}
attrs = append ( attrs , & ldap . EntryAttribute { Name : "member" , Values : g . member } )
attrs = append ( attrs , & ldap . EntryAttribute { Name : "goauthentik.io/ldap/superuser" , Values : [ ] string { BoolToString ( g . isSuperuser ) } } )
2021-05-24 14:09:05 +00:00
2021-07-14 18:37:27 +00:00
if g . akAttributes != nil {
2021-07-14 07:17:01 +00:00
attrs = append ( attrs , AKAttrsToLDAP ( g . akAttributes ) ... )
}
return & ldap . Entry { DN : g . dn , Attributes : attrs }
2021-05-24 14:09:05 +00:00
}