LDAP group backend for GraphAPI
This is an initial implementation of the /groups graph endpoint. Currently it is only showing the ID and Name attributes of the groups. Listing members is not yet supported. As the userbackend this is still read-only and doesn't support any advanced filtering.
This commit is contained in:
@@ -53,16 +53,23 @@ type Spaces struct {
|
||||
}
|
||||
|
||||
type LDAP struct {
|
||||
URI string `ocisConfig:"uri"`
|
||||
BindDN string `ocisConfig:"bind_dn"`
|
||||
BindPassword string `ocisConfig:"bind_password"`
|
||||
URI string `ocisConfig:"uri"`
|
||||
BindDN string `ocisConfig:"bind_dn"`
|
||||
BindPassword string `ocisConfig:"bind_password"`
|
||||
|
||||
UserBaseDN string `ocisConfig:"user_base_dn"`
|
||||
UserSearchScope string `ocisConfig:"user_search_scope"`
|
||||
UserFilter string `ocisConfig:"user_filter"`
|
||||
UserEmailAttribute string `ocisConfig:"user_mail_attribute"`
|
||||
UserDisplayNameAttribute string `ocisConfig:"user_displayname_attribute"`
|
||||
UserNameAttribute string `ocisConfig:"user_name_attribute"`
|
||||
UserIDAttribute string `ocisConfig:"user_id_attribute"`
|
||||
UserFilter string `ocisConfig:"user_filter"`
|
||||
UserSearchScope string `ocisConfig:"user_search_scope"`
|
||||
|
||||
GroupBaseDN string `ocisConfig:"group_base_dn"`
|
||||
GroupSearchScope string `ocisConfig:"group_search_scope"`
|
||||
GroupFilter string `ocisConfig:"group_filter"`
|
||||
GroupNameAttribute string `ocisConfig:"group_name_attribute"`
|
||||
GroupIDAttribute string `ocisConfig:"group_id_attribute"`
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
@@ -129,14 +136,19 @@ func DefaultConfig() *Config {
|
||||
BindDN: "",
|
||||
BindPassword: "",
|
||||
UserBaseDN: "ou=users,dc=ocis,dc=test",
|
||||
UserSearchScope: "sub",
|
||||
UserFilter: "(objectClass=posixaccount)",
|
||||
UserEmailAttribute: "mail",
|
||||
UserDisplayNameAttribute: "displayName",
|
||||
UserNameAttribute: "uid",
|
||||
// FIXME: switch this to some more widely available attribute by default
|
||||
// ideally this needs to be constant for the lifetime of a users
|
||||
UserIDAttribute: "ownclouduuid",
|
||||
UserFilter: "(objectClass=posixaccount)",
|
||||
UserSearchScope: "sub",
|
||||
UserIDAttribute: "ownclouduuid",
|
||||
GroupBaseDN: "ou=groups,dc=ocis,dc=test",
|
||||
GroupSearchScope: "sub",
|
||||
GroupFilter: "(objectclass=groupOfNames)",
|
||||
GroupNameAttribute: "cn",
|
||||
GroupIDAttribute: "cn",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -155,5 +155,25 @@ func structMappings(cfg *Config) []shared.EnvBinding {
|
||||
EnvVars: []string{"GRAPH_LDAP_USER_SCOPE"},
|
||||
Destination: &cfg.Identity.LDAP.UserSearchScope,
|
||||
},
|
||||
{
|
||||
EnvVars: []string{"GRAPH_LDAP_GROUP_BASE_DN"},
|
||||
Destination: &cfg.Identity.LDAP.GroupBaseDN,
|
||||
},
|
||||
{
|
||||
EnvVars: []string{"GRAPH_LDAP_GROUP_SEARCH_SCOPE"},
|
||||
Destination: &cfg.Identity.LDAP.GroupSearchScope,
|
||||
},
|
||||
{
|
||||
EnvVars: []string{"GRAPH_LDAP_GROUP_FILTER"},
|
||||
Destination: &cfg.Identity.LDAP.GroupFilter,
|
||||
},
|
||||
{
|
||||
EnvVars: []string{"GRAPH_LDAP_GROUP_NAME_ATTRIBUTE"},
|
||||
Destination: &cfg.Identity.LDAP.GroupNameAttribute,
|
||||
},
|
||||
{
|
||||
EnvVars: []string{"GRAPH_LDAP_GROUP_ID_ATTRIBUTE"},
|
||||
Destination: &cfg.Identity.LDAP.GroupIDAttribute,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+129
-20
@@ -19,8 +19,14 @@ type LDAP struct {
|
||||
userFilter string
|
||||
userScope int
|
||||
userAttributeMap userAttributeMap
|
||||
logger *log.Logger
|
||||
conn *ldaputil.ConnWithReconnect
|
||||
|
||||
groupBaseDN string
|
||||
groupFilter string
|
||||
groupScope int
|
||||
groupAttributeMap groupAttributeMap
|
||||
|
||||
logger *log.Logger
|
||||
conn *ldaputil.ConnWithReconnect
|
||||
}
|
||||
|
||||
type userAttributeMap struct {
|
||||
@@ -30,7 +36,12 @@ type userAttributeMap struct {
|
||||
userName string
|
||||
}
|
||||
|
||||
func NewLDAPBackend(config config.LDAP, logger *log.Logger) *LDAP {
|
||||
type groupAttributeMap struct {
|
||||
name string
|
||||
id string
|
||||
}
|
||||
|
||||
func NewLDAPBackend(config config.LDAP, logger *log.Logger) (*LDAP, error) {
|
||||
conn := ldaputil.NewLDAPWithReconnect(logger, config.URI, config.BindDN, config.BindPassword)
|
||||
uam := userAttributeMap{
|
||||
displayName: config.UserDisplayNameAttribute,
|
||||
@@ -38,25 +49,33 @@ func NewLDAPBackend(config config.LDAP, logger *log.Logger) *LDAP {
|
||||
mail: config.UserEmailAttribute,
|
||||
userName: config.UserNameAttribute,
|
||||
}
|
||||
gam := groupAttributeMap{
|
||||
name: config.GroupNameAttribute,
|
||||
id: config.GroupIDAttribute,
|
||||
}
|
||||
|
||||
var userScope int
|
||||
switch config.UserSearchScope {
|
||||
case "sub":
|
||||
userScope = ldap.ScopeWholeSubtree
|
||||
case "one":
|
||||
userScope = ldap.ScopeSingleLevel
|
||||
case "base":
|
||||
userScope = ldap.ScopeBaseObject
|
||||
var userScope, groupScope int
|
||||
var err error
|
||||
if userScope, err = stringToScope(config.UserSearchScope); err != nil {
|
||||
return nil, fmt.Errorf("Error configuring user scope: %w", err)
|
||||
}
|
||||
|
||||
if groupScope, err = stringToScope(config.GroupSearchScope); err != nil {
|
||||
return nil, fmt.Errorf("Error configuring group scope: %w", err)
|
||||
}
|
||||
|
||||
return &LDAP{
|
||||
userBaseDN: config.UserBaseDN,
|
||||
userFilter: config.UserFilter,
|
||||
userScope: userScope,
|
||||
userAttributeMap: uam,
|
||||
logger: logger,
|
||||
conn: &conn,
|
||||
}
|
||||
userBaseDN: config.UserBaseDN,
|
||||
userFilter: config.UserFilter,
|
||||
userScope: userScope,
|
||||
userAttributeMap: uam,
|
||||
groupBaseDN: config.GroupBaseDN,
|
||||
groupFilter: config.GroupFilter,
|
||||
groupScope: groupScope,
|
||||
groupAttributeMap: gam,
|
||||
logger: logger,
|
||||
conn: &conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (i *LDAP) GetUser(ctx context.Context, userID string) (*msgraph.User, error) {
|
||||
@@ -137,11 +156,75 @@ func (i *LDAP) GetUsers(ctx context.Context, queryParam url.Values) ([]*msgraph.
|
||||
}
|
||||
|
||||
func (i *LDAP) GetGroup(ctx context.Context, groupID string) (*msgraph.Group, error) {
|
||||
return nil, nil
|
||||
i.logger.Debug().Str("backend", "ldap").Msg("GetGroup")
|
||||
groupID = ldap.EscapeFilter(groupID)
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
i.groupBaseDN, i.groupScope, ldap.NeverDerefAliases, 1, 0, false,
|
||||
fmt.Sprintf("(&%s(|(%s=%s)(%s=%s)))", i.groupFilter, i.groupAttributeMap.name, groupID, i.groupAttributeMap.id, groupID),
|
||||
[]string{
|
||||
i.groupAttributeMap.name,
|
||||
i.groupAttributeMap.id,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
i.logger.Debug().Str("backend", "ldap").Msgf("Search %s", i.groupBaseDN)
|
||||
res, err := i.conn.Search(searchRequest)
|
||||
|
||||
if err != nil {
|
||||
var errmsg string
|
||||
if lerr, ok := err.(*ldap.Error); ok {
|
||||
if lerr.ResultCode == ldap.LDAPResultSizeLimitExceeded {
|
||||
errmsg = fmt.Sprintf("too many results searching for group '%s'", groupID)
|
||||
i.logger.Debug().Str("backend", "ldap").Err(lerr).Msg(errmsg)
|
||||
}
|
||||
}
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, errmsg)
|
||||
}
|
||||
if len(res.Entries) == 0 {
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, "not found")
|
||||
}
|
||||
|
||||
return i.createGroupModelFromLDAP(res.Entries[0]), nil
|
||||
}
|
||||
|
||||
func (i *LDAP) GetGroups(ctx context.Context, queryParam url.Values) ([]*msgraph.Group, error) {
|
||||
return nil, nil
|
||||
i.logger.Debug().Str("backend", "ldap").Msg("GetGroups")
|
||||
|
||||
search := queryParam.Get("search")
|
||||
if search == "" {
|
||||
search = queryParam.Get("$search")
|
||||
}
|
||||
groupFilter := i.groupFilter
|
||||
if search != "" {
|
||||
search = ldap.EscapeFilter(search)
|
||||
groupFilter = fmt.Sprintf(
|
||||
"(&(%s)(|(%s=%s*)(%s=%s*)))",
|
||||
groupFilter,
|
||||
i.groupAttributeMap.name, search,
|
||||
i.groupAttributeMap.id, search,
|
||||
)
|
||||
}
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
i.groupBaseDN, i.groupScope, ldap.NeverDerefAliases, 0, 0, false,
|
||||
groupFilter,
|
||||
[]string{
|
||||
i.groupAttributeMap.name,
|
||||
i.groupAttributeMap.id,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
i.logger.Debug().Str("backend", "ldap").Str("Base", i.groupBaseDN).Str("filter", groupFilter).Msg("ldap search")
|
||||
res, err := i.conn.Search(searchRequest)
|
||||
if err != nil {
|
||||
return nil, errorcode.New(errorcode.ItemNotFound, err.Error())
|
||||
}
|
||||
|
||||
groups := make([]*msgraph.Group, 0, len(res.Entries))
|
||||
|
||||
for _, e := range res.Entries {
|
||||
groups = append(groups, i.createGroupModelFromLDAP(e))
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *msgraph.User {
|
||||
@@ -157,9 +240,35 @@ func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *msgraph.User {
|
||||
}
|
||||
}
|
||||
|
||||
func (i *LDAP) createGroupModelFromLDAP(e *ldap.Entry) *msgraph.Group {
|
||||
return &msgraph.Group{
|
||||
DisplayName: pointerOrNil(e.GetEqualFoldAttributeValue(i.groupAttributeMap.name)),
|
||||
OnPremisesSamAccountName: pointerOrNil(e.GetEqualFoldAttributeValue(i.groupAttributeMap.name)),
|
||||
DirectoryObject: msgraph.DirectoryObject{
|
||||
Entity: msgraph.Entity{
|
||||
ID: pointerOrNil(e.GetEqualFoldAttributeValue(i.groupAttributeMap.id)),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
func pointerOrNil(val string) *string {
|
||||
if val == "" {
|
||||
return nil
|
||||
}
|
||||
return &val
|
||||
}
|
||||
|
||||
func stringToScope(scope string) (int, error) {
|
||||
var s int
|
||||
switch scope {
|
||||
case "sub":
|
||||
s = ldap.ScopeWholeSubtree
|
||||
case "one":
|
||||
s = ldap.ScopeSingleLevel
|
||||
case "base":
|
||||
s = ldap.ScopeBaseObject
|
||||
default:
|
||||
return 0, fmt.Errorf("Invalid Scope '%s'", scope)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -33,9 +33,14 @@ func NewService(opts ...Option) Service {
|
||||
Logger: &options.Logger,
|
||||
}
|
||||
case "ldap":
|
||||
backend = identity.NewLDAPBackend(options.Config.Identity.LDAP, &options.Logger)
|
||||
var err error
|
||||
if backend, err = identity.NewLDAPBackend(options.Config.Identity.LDAP, &options.Logger); err != nil {
|
||||
options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err)
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
|
||||
return nil
|
||||
}
|
||||
|
||||
svc := Graph{
|
||||
|
||||
Reference in New Issue
Block a user