LDAP user backend for GraphAPI
This is still read-only and doesn't support any of the advanced querying options of the graph API.
This commit is contained in:
@@ -52,8 +52,22 @@ type Spaces struct {
|
|||||||
DefaultQuota string `ocisConfig:"default_quota"`
|
DefaultQuota string `ocisConfig:"default_quota"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LDAP struct {
|
||||||
|
URI string `ocisConfig:"uri"`
|
||||||
|
BindDN string `ocisConfig:"bind_dn"`
|
||||||
|
BindPassword string `ocisConfig:"bind_password"`
|
||||||
|
UserBaseDN string `ocisConfig:"user_base_dn"`
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
Backend string `ocisConfig:"backend"`
|
Backend string `ocisConfig:"backend"`
|
||||||
|
LDAP LDAP `ocisConfig:"ldap"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config combines all available configuration parts.
|
// Config combines all available configuration parts.
|
||||||
@@ -110,6 +124,20 @@ func DefaultConfig() *Config {
|
|||||||
},
|
},
|
||||||
Identity: Identity{
|
Identity: Identity{
|
||||||
Backend: "cs3",
|
Backend: "cs3",
|
||||||
|
LDAP: LDAP{
|
||||||
|
URI: "ldap://localhost:9125",
|
||||||
|
BindDN: "",
|
||||||
|
BindPassword: "",
|
||||||
|
UserBaseDN: "ou=users,dc=ocis,dc=test",
|
||||||
|
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",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,5 +115,45 @@ func structMappings(cfg *Config) []shared.EnvBinding {
|
|||||||
EnvVars: []string{"GRAPH_IDENTITY_BACKEND"},
|
EnvVars: []string{"GRAPH_IDENTITY_BACKEND"},
|
||||||
Destination: &cfg.Identity.Backend,
|
Destination: &cfg.Identity.Backend,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_URI"},
|
||||||
|
Destination: &cfg.Identity.LDAP.URI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_BIND_DN"},
|
||||||
|
Destination: &cfg.Identity.LDAP.BindDN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_BIND_PASSWORD"},
|
||||||
|
Destination: &cfg.Identity.LDAP.BindPassword,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_USER_BASE_DN"},
|
||||||
|
Destination: &cfg.Identity.LDAP.UserBaseDN,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_USER_EMAIL_ATTRIBUTE"},
|
||||||
|
Destination: &cfg.Identity.LDAP.UserEmailAttribute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_USER_DISPLAYNAME_ATTRIBUTE"},
|
||||||
|
Destination: &cfg.Identity.LDAP.UserDisplayNameAttribute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_USER_NAME_ATTRIBUTE"},
|
||||||
|
Destination: &cfg.Identity.LDAP.UserNameAttribute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_USER_UID_ATTRIBUTE"},
|
||||||
|
Destination: &cfg.Identity.LDAP.UserIDAttribute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_USER_FILTER"},
|
||||||
|
Destination: &cfg.Identity.LDAP.UserFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EnvVars: []string{"GRAPH_LDAP_USER_SCOPE"},
|
||||||
|
Destination: &cfg.Identity.LDAP.UserSearchScope,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
package identity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
msgraph "github.com/yaegashi/msgraph.go/beta"
|
||||||
|
|
||||||
|
"github.com/owncloud/ocis/graph/pkg/config"
|
||||||
|
ldaputil "github.com/owncloud/ocis/graph/pkg/identity/ldap"
|
||||||
|
"github.com/owncloud/ocis/graph/pkg/service/v0/errorcode"
|
||||||
|
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LDAP struct {
|
||||||
|
userBaseDN string
|
||||||
|
userFilter string
|
||||||
|
userScope int
|
||||||
|
userAttributeMap userAttributeMap
|
||||||
|
logger *log.Logger
|
||||||
|
conn *ldaputil.ConnWithReconnect
|
||||||
|
}
|
||||||
|
|
||||||
|
type userAttributeMap struct {
|
||||||
|
displayName string
|
||||||
|
id string
|
||||||
|
mail string
|
||||||
|
userName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLDAPBackend(config config.LDAP, logger *log.Logger) *LDAP {
|
||||||
|
conn := ldaputil.NewLDAPWithReconnect(logger, config.URI, config.BindDN, config.BindPassword)
|
||||||
|
uam := userAttributeMap{
|
||||||
|
displayName: config.UserDisplayNameAttribute,
|
||||||
|
id: config.UserIDAttribute,
|
||||||
|
mail: config.UserEmailAttribute,
|
||||||
|
userName: config.UserNameAttribute,
|
||||||
|
}
|
||||||
|
|
||||||
|
var userScope int
|
||||||
|
switch config.UserSearchScope {
|
||||||
|
case "sub":
|
||||||
|
userScope = ldap.ScopeWholeSubtree
|
||||||
|
case "one":
|
||||||
|
userScope = ldap.ScopeSingleLevel
|
||||||
|
case "base":
|
||||||
|
userScope = ldap.ScopeBaseObject
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LDAP{
|
||||||
|
userBaseDN: config.UserBaseDN,
|
||||||
|
userFilter: config.UserFilter,
|
||||||
|
userScope: userScope,
|
||||||
|
userAttributeMap: uam,
|
||||||
|
logger: logger,
|
||||||
|
conn: &conn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *LDAP) GetUser(ctx context.Context, userID string) (*msgraph.User, error) {
|
||||||
|
i.logger.Debug().Str("backend", "ldap").Msg("GetUser")
|
||||||
|
userID = ldap.EscapeFilter(userID)
|
||||||
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
i.userBaseDN, i.userScope, ldap.NeverDerefAliases, 1, 0, false,
|
||||||
|
fmt.Sprintf("(&%s(|(%s=%s)(%s=%s)))", i.userFilter, i.userAttributeMap.userName, userID, i.userAttributeMap.id, userID),
|
||||||
|
[]string{
|
||||||
|
i.userAttributeMap.displayName,
|
||||||
|
i.userAttributeMap.id,
|
||||||
|
i.userAttributeMap.mail,
|
||||||
|
i.userAttributeMap.userName,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
i.logger.Debug().Str("backend", "ldap").Msgf("Search %s", i.userBaseDN)
|
||||||
|
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 user '%s'", userID)
|
||||||
|
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.createUserModelFromLDAP(res.Entries[0]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *LDAP) GetUsers(ctx context.Context, queryParam url.Values) ([]*msgraph.User, error) {
|
||||||
|
i.logger.Debug().Str("backend", "ldap").Msg("GetUsers")
|
||||||
|
|
||||||
|
search := queryParam.Get("search")
|
||||||
|
if search == "" {
|
||||||
|
search = queryParam.Get("$search")
|
||||||
|
}
|
||||||
|
userFilter := i.userFilter
|
||||||
|
if search != "" {
|
||||||
|
search = ldap.EscapeFilter(search)
|
||||||
|
userFilter = fmt.Sprintf(
|
||||||
|
"(&(%s)(|(%s=%s*)(%s=%s*)(%s=%s*)))",
|
||||||
|
userFilter,
|
||||||
|
i.userAttributeMap.userName, search,
|
||||||
|
i.userAttributeMap.mail, search,
|
||||||
|
i.userAttributeMap.displayName, search,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
i.userBaseDN, i.userScope, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
userFilter,
|
||||||
|
[]string{
|
||||||
|
i.userAttributeMap.displayName,
|
||||||
|
i.userAttributeMap.id,
|
||||||
|
i.userAttributeMap.mail,
|
||||||
|
i.userAttributeMap.userName,
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
i.logger.Debug().Str("backend", "ldap").Msgf("Search %s", i.userBaseDN)
|
||||||
|
res, err := i.conn.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorcode.New(errorcode.ItemNotFound, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
users := make([]*msgraph.User, 0, len(res.Entries))
|
||||||
|
|
||||||
|
for _, e := range res.Entries {
|
||||||
|
users = append(users, i.createUserModelFromLDAP(e))
|
||||||
|
}
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *LDAP) createUserModelFromLDAP(e *ldap.Entry) *msgraph.User {
|
||||||
|
return &msgraph.User{
|
||||||
|
DisplayName: pointerOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.displayName)),
|
||||||
|
Mail: pointerOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.mail)),
|
||||||
|
OnPremisesSamAccountName: pointerOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.userName)),
|
||||||
|
DirectoryObject: msgraph.DirectoryObject{
|
||||||
|
Entity: msgraph.Entity{
|
||||||
|
ID: pointerOrNil(e.GetEqualFoldAttributeValue(i.userAttributeMap.id)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pointerOrNil(val string) *string {
|
||||||
|
if val == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &val
|
||||||
|
}
|
||||||
@@ -32,6 +32,8 @@ func NewService(opts ...Option) Service {
|
|||||||
Config: &options.Config.Reva,
|
Config: &options.Config.Reva,
|
||||||
Logger: &options.Logger,
|
Logger: &options.Logger,
|
||||||
}
|
}
|
||||||
|
case "ldap":
|
||||||
|
userBackend = identity.NewLDAPBackend(options.Config.Identity.LDAP, &options.Logger)
|
||||||
default:
|
default:
|
||||||
options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
|
options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user