forked from casdoor/casdoor
feat: add LDAP server attribute filtering per organization (#5222)
This commit is contained in:
@@ -203,49 +203,101 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
return
|
||||
}
|
||||
|
||||
orgCache := make(map[string]*object.Organization)
|
||||
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("uid=%s,cn=%s,%s", user.Id, user.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
uidNumberStr := fmt.Sprintf("%v", hash(user.Name))
|
||||
if _, ok := orgCache[user.Owner]; !ok {
|
||||
org, err := object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
log.Printf("handleSearch: failed to get organization for user %s: %v", user.Name, err)
|
||||
}
|
||||
orgCache[user.Owner] = org
|
||||
}
|
||||
org := orgCache[user.Owner]
|
||||
|
||||
e := buildUserSearchEntry(user, string(r.BaseObject()), resolveRequestAttributes(r.Attributes()), org)
|
||||
w.Write(e)
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
// resolveRequestAttributes expands the "*" wildcard to the full list of additional LDAP attributes.
|
||||
func resolveRequestAttributes(attrs message.AttributeSelection) []string {
|
||||
result := make([]string, 0, len(attrs))
|
||||
for _, attr := range attrs {
|
||||
if string(attr) == "*" {
|
||||
result = make([]string, 0, len(AdditionalLdapAttributes))
|
||||
for _, a := range AdditionalLdapAttributes {
|
||||
result = append(result, string(a))
|
||||
}
|
||||
return result
|
||||
}
|
||||
result = append(result, string(attr))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// buildUserSearchEntry constructs an LDAP search result entry for the given user,
|
||||
// respecting the organization's LdapAttributes filter.
|
||||
func buildUserSearchEntry(user *object.User, baseDN string, attrs []string, org *object.Organization) message.SearchResultEntry {
|
||||
dn := fmt.Sprintf("uid=%s,cn=%s,%s", user.Id, user.Name, baseDN)
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
uidNumberStr := fmt.Sprintf("%v", hash(user.Name))
|
||||
if IsLdapAttrAllowed(org, "uidNumber") {
|
||||
e.AddAttribute("uidNumber", message.AttributeValue(uidNumberStr))
|
||||
}
|
||||
if IsLdapAttrAllowed(org, "gidNumber") {
|
||||
e.AddAttribute("gidNumber", message.AttributeValue(uidNumberStr))
|
||||
}
|
||||
if IsLdapAttrAllowed(org, "homeDirectory") {
|
||||
e.AddAttribute("homeDirectory", message.AttributeValue("/home/"+user.Name))
|
||||
}
|
||||
if IsLdapAttrAllowed(org, "cn") {
|
||||
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||
}
|
||||
if IsLdapAttrAllowed(org, "uid") {
|
||||
e.AddAttribute("uid", message.AttributeValue(user.Id))
|
||||
}
|
||||
if IsLdapAttrAllowed(org, "mail") {
|
||||
e.AddAttribute("mail", message.AttributeValue(user.Email))
|
||||
}
|
||||
if IsLdapAttrAllowed(org, "mobile") {
|
||||
e.AddAttribute("mobile", message.AttributeValue(user.Phone))
|
||||
}
|
||||
if IsLdapAttrAllowed(org, "sn") {
|
||||
e.AddAttribute("sn", message.AttributeValue(user.LastName))
|
||||
}
|
||||
if IsLdapAttrAllowed(org, "givenName") {
|
||||
e.AddAttribute("givenName", message.AttributeValue(user.FirstName))
|
||||
// Add POSIX attributes for Linux machine login support
|
||||
}
|
||||
// Add POSIX attributes for Linux machine login support
|
||||
if IsLdapAttrAllowed(org, "loginShell") {
|
||||
e.AddAttribute("loginShell", getAttribute("loginShell", user))
|
||||
}
|
||||
if IsLdapAttrAllowed(org, "gecos") {
|
||||
e.AddAttribute("gecos", getAttribute("gecos", user))
|
||||
// Add SSH public key if available
|
||||
}
|
||||
// Add SSH public key if available
|
||||
if IsLdapAttrAllowed(org, "sshPublicKey") {
|
||||
sshKey := getAttribute("sshPublicKey", user)
|
||||
if sshKey != "" {
|
||||
e.AddAttribute("sshPublicKey", sshKey)
|
||||
}
|
||||
// Add objectClass for posixAccount
|
||||
e.AddAttribute("objectClass", "posixAccount")
|
||||
}
|
||||
// Add objectClass for posixAccount
|
||||
e.AddAttribute("objectClass", "posixAccount")
|
||||
if IsLdapAttrAllowed(org, ldapMemberOfAttr) {
|
||||
for _, group := range user.Groups {
|
||||
e.AddAttribute(ldapMemberOfAttr, message.AttributeValue(group))
|
||||
}
|
||||
attrs := r.Attributes()
|
||||
for _, attr := range attrs {
|
||||
if string(attr) == "*" {
|
||||
attrs = AdditionalLdapAttributes
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
||||
if string(attr) == "title" {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
||||
}
|
||||
}
|
||||
|
||||
w.Write(e)
|
||||
}
|
||||
w.Write(res)
|
||||
for _, attr := range attrs {
|
||||
if !IsLdapAttrAllowed(org, attr) {
|
||||
continue
|
||||
}
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(attr, user))
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func handleRootSearch(w ldap.ResponseWriter, r *message.SearchRequest, res *message.SearchResultDone, m *ldap.Message) {
|
||||
|
||||
14
ldap/util.go
14
ldap/util.go
@@ -198,6 +198,20 @@ func stringInSlice(value string, list []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsLdapAttrAllowed checks whether the given LDAP attribute is allowed for the organization.
|
||||
// An empty filter or a filter containing "All" means all attributes are allowed.
|
||||
func IsLdapAttrAllowed(org *object.Organization, attr string) bool {
|
||||
if org == nil || len(org.LdapAttributes) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, f := range org.LdapAttributes {
|
||||
if strings.EqualFold(f, "All") || strings.EqualFold(f, attr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
|
||||
switch f := filter.(type) {
|
||||
case message.FilterAnd:
|
||||
|
||||
@@ -94,6 +94,8 @@ type Organization struct {
|
||||
|
||||
DcrPolicy string `xorm:"varchar(100)" json:"dcrPolicy"`
|
||||
|
||||
LdapAttributes []string `xorm:"mediumtext" json:"ldapAttributes"`
|
||||
|
||||
OrgBalance float64 `json:"orgBalance"`
|
||||
UserBalance float64 `json:"userBalance"`
|
||||
BalanceCredit float64 `json:"balanceCredit"`
|
||||
|
||||
@@ -747,6 +747,43 @@ class OrganizationEditPage extends React.Component {
|
||||
}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("organization:LDAP attributes"), i18next.t("organization:LDAP attributes - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
style={{width: "100%"}}
|
||||
value={this.state.organization.ldapAttributes ?? []}
|
||||
onChange={(value) => {
|
||||
this.updateOrganizationField("ldapAttributes", value);
|
||||
}}
|
||||
options={[
|
||||
{value: "uid", label: "uid"},
|
||||
{value: "cn", label: "cn"},
|
||||
{value: "mail", label: "mail"},
|
||||
{value: "email", label: "email"},
|
||||
{value: "mobile", label: "mobile"},
|
||||
{value: "displayName", label: "displayName"},
|
||||
{value: "givenName", label: "givenName"},
|
||||
{value: "sn", label: "sn"},
|
||||
{value: "uidNumber", label: "uidNumber"},
|
||||
{value: "gidNumber", label: "gidNumber"},
|
||||
{value: "homeDirectory", label: "homeDirectory"},
|
||||
{value: "loginShell", label: "loginShell"},
|
||||
{value: "gecos", label: "gecos"},
|
||||
{value: "sshPublicKey", label: "sshPublicKey"},
|
||||
{value: "memberOf", label: "memberOf"},
|
||||
{value: "title", label: "title"},
|
||||
{value: "userPassword", label: "userPassword"},
|
||||
{value: "c", label: "c"},
|
||||
{value: "co", label: "co"},
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}}>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :
|
||||
|
||||
Reference in New Issue
Block a user