2023-04-04 20:51:28 +08:00
|
|
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package ldap
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"strings"
|
2024-01-05 09:24:12 +08:00
|
|
|
"time"
|
2023-04-04 20:51:28 +08:00
|
|
|
|
|
|
|
"github.com/casdoor/casdoor/object"
|
|
|
|
"github.com/casdoor/casdoor/util"
|
2023-04-13 14:12:31 +08:00
|
|
|
"github.com/lor00x/goldap/message"
|
2023-04-04 20:51:28 +08:00
|
|
|
|
|
|
|
ldap "github.com/forestmgy/ldapserver"
|
2023-11-26 18:11:49 +03:00
|
|
|
|
|
|
|
"github.com/xorm-io/builder"
|
2023-04-04 20:51:28 +08:00
|
|
|
)
|
|
|
|
|
2024-01-05 09:24:12 +08:00
|
|
|
type V = message.AttributeValue
|
2023-11-26 18:11:49 +03:00
|
|
|
|
2024-01-05 09:24:12 +08:00
|
|
|
type UserAttributeMapper func(user *object.User) []V
|
|
|
|
|
|
|
|
type UserFieldRelation struct {
|
2023-11-26 18:11:49 +03:00
|
|
|
userField string
|
2024-01-05 09:24:12 +08:00
|
|
|
ldapField string
|
2023-11-26 18:11:49 +03:00
|
|
|
notSearchable bool
|
|
|
|
hideOnStarOp bool
|
2024-01-05 09:24:12 +08:00
|
|
|
fieldMapper UserAttributeMapper
|
|
|
|
constantValue []V
|
2023-11-26 18:11:49 +03:00
|
|
|
}
|
|
|
|
|
2024-01-05 09:24:12 +08:00
|
|
|
func (rel UserFieldRelation) GetField() (string, error) {
|
2023-11-26 18:11:49 +03:00
|
|
|
if rel.notSearchable {
|
|
|
|
return "", fmt.Errorf("attribute %s not supported", rel.userField)
|
|
|
|
}
|
|
|
|
return rel.userField, nil
|
|
|
|
}
|
|
|
|
|
2024-01-05 09:24:12 +08:00
|
|
|
func (rel UserFieldRelation) GetAttributeValues(user *object.User) []V {
|
|
|
|
if rel.constantValue != nil && rel.fieldMapper == nil {
|
|
|
|
return rel.constantValue
|
|
|
|
}
|
2023-11-26 18:11:49 +03:00
|
|
|
return rel.fieldMapper(user)
|
|
|
|
}
|
|
|
|
|
2024-01-05 09:24:12 +08:00
|
|
|
type UserFieldRelationMap map[string]UserFieldRelation
|
|
|
|
|
|
|
|
func (m UserFieldRelationMap) CaseInsensitiveGet(key string) (UserFieldRelation, bool) {
|
|
|
|
lowerKey := strings.ToLower(key)
|
|
|
|
ret, ok := m[lowerKey]
|
|
|
|
return ret, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
type GroupAttributeMapper func(group *object.Group) []V
|
|
|
|
|
|
|
|
type GroupFieldRelation struct {
|
|
|
|
groupField string
|
|
|
|
ldapField string
|
|
|
|
notSearchable bool
|
|
|
|
hideOnStarOp bool
|
|
|
|
fieldMapper GroupAttributeMapper
|
|
|
|
constantValue []V
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rel GroupFieldRelation) GetField() (string, error) {
|
|
|
|
if rel.notSearchable {
|
|
|
|
return "", fmt.Errorf("attribute %s not supported", rel.groupField)
|
|
|
|
}
|
|
|
|
return rel.groupField, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (rel GroupFieldRelation) GetAttributeValues(group *object.Group) []V {
|
|
|
|
if rel.constantValue != nil && rel.fieldMapper == nil {
|
|
|
|
return rel.constantValue
|
|
|
|
}
|
|
|
|
return rel.fieldMapper(group)
|
|
|
|
}
|
|
|
|
|
|
|
|
type GroupFieldRelationMap map[string]GroupFieldRelation
|
|
|
|
|
|
|
|
func (m GroupFieldRelationMap) CaseInsensitiveGet(key string) (GroupFieldRelation, bool) {
|
|
|
|
lowerKey := strings.ToLower(key)
|
|
|
|
ret, ok := m[lowerKey]
|
|
|
|
return ret, ok
|
|
|
|
}
|
|
|
|
|
|
|
|
var ldapUserAttributesMapping = UserFieldRelationMap{
|
|
|
|
"cn": {ldapField: "cn", userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.Name)}
|
|
|
|
}},
|
|
|
|
"uid": {ldapField: "uid", userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.Name)}
|
|
|
|
}},
|
|
|
|
"displayname": {ldapField: "displayName", userField: "displayName", fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.DisplayName)}
|
|
|
|
}},
|
|
|
|
"email": {ldapField: "email", userField: "email", fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.Email)}
|
|
|
|
}},
|
|
|
|
"mail": {ldapField: "mail", userField: "email", fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.Email)}
|
2023-11-26 18:11:49 +03:00
|
|
|
}},
|
2024-01-05 09:24:12 +08:00
|
|
|
"mobile": {ldapField: "mobile", userField: "phone", fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.Phone)}
|
2023-11-26 18:11:49 +03:00
|
|
|
}},
|
2024-01-05 09:24:12 +08:00
|
|
|
"telephonenumber": {ldapField: "telephoneNumber", userField: "phone", fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.Phone)}
|
2023-11-26 18:11:49 +03:00
|
|
|
}},
|
2024-01-05 09:24:12 +08:00
|
|
|
"postaladdress": {ldapField: "postalAddress", userField: "address", fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(strings.Join(user.Address, " "))}
|
2023-11-26 18:11:49 +03:00
|
|
|
}},
|
2024-01-05 09:24:12 +08:00
|
|
|
"title": {ldapField: "title", userField: "title", fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.Title)}
|
2023-11-26 18:11:49 +03:00
|
|
|
}},
|
2024-01-05 09:24:12 +08:00
|
|
|
"gecos": {ldapField: "gecos", userField: "displayName", fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.DisplayName)}
|
2023-11-26 18:11:49 +03:00
|
|
|
}},
|
2024-01-05 09:24:12 +08:00
|
|
|
"description": {ldapField: "description", userField: "displayName", fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(user.DisplayName)}
|
2023-11-26 18:11:49 +03:00
|
|
|
}},
|
2024-01-05 09:24:12 +08:00
|
|
|
"logindisabled": {ldapField: "loginDisabled", userField: "isForbidden", fieldMapper: func(user *object.User) []V {
|
|
|
|
if user.IsForbidden {
|
|
|
|
return []V{V("1")}
|
|
|
|
} else {
|
|
|
|
return []V{V("0")}
|
|
|
|
}
|
|
|
|
}},
|
|
|
|
"userpassword": {
|
|
|
|
ldapField: "userPassword",
|
2023-11-26 18:11:49 +03:00
|
|
|
userField: "userPassword",
|
|
|
|
notSearchable: true,
|
2024-01-05 09:24:12 +08:00
|
|
|
fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(getUserPasswordWithType(user))}
|
2023-11-26 18:11:49 +03:00
|
|
|
},
|
|
|
|
},
|
2024-01-05 09:24:12 +08:00
|
|
|
"uidnumber": {ldapField: "uidNumber", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V(fmt.Sprintf("%v", hash(user.Name)))}
|
|
|
|
}},
|
|
|
|
"gidnumber": {ldapField: "gidNumber", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
if len(user.Groups) == 0 {
|
|
|
|
return []V{V("")}
|
|
|
|
}
|
|
|
|
group, err := object.GetGroup(user.Groups[0])
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("gidnumber object.GetGroup error: %s", err)
|
|
|
|
return []V{V("")}
|
|
|
|
}
|
|
|
|
return []V{V(fmt.Sprintf("%v", hash(group.Name)))}
|
|
|
|
}},
|
|
|
|
"homedirectory": {ldapField: "homeDirectory", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
return []V{V("/home/" + user.Name)}
|
|
|
|
}},
|
|
|
|
"loginshell": {ldapField: "loginShell", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
if user.IsForbidden || user.IsDeleted {
|
|
|
|
return []V{V("/sbin/nologin")}
|
|
|
|
} else {
|
|
|
|
return []V{V("/bin/bash")}
|
|
|
|
}
|
|
|
|
}},
|
|
|
|
"shadowlastchange": {ldapField: "shadowLastChange", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
// "this attribute specifies number of days between January 1, 1970, and the date that the password was last modified"
|
|
|
|
updatedTime, err := time.Parse(time.RFC3339, user.UpdatedTime)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("shadowlastchange time.Parse error: %s", err)
|
|
|
|
updatedTime = time.Now()
|
|
|
|
}
|
|
|
|
return []V{V(fmt.Sprint(updatedTime.Unix() / 86400))}
|
|
|
|
}},
|
|
|
|
"pwdchangedtime": {ldapField: "pwdChangedTime", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
updatedTime, err := time.Parse(time.RFC3339, user.UpdatedTime)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("pwdchangedtime time.Parse error: %s", err)
|
|
|
|
updatedTime = time.Now()
|
|
|
|
}
|
|
|
|
return []V{V(updatedTime.UTC().Format("20060102030405Z"))}
|
|
|
|
}},
|
|
|
|
"shadowmin": {ldapField: "shadowMin", notSearchable: true, constantValue: []V{V("0")}},
|
|
|
|
"shadowmax": {ldapField: "shadowMax", notSearchable: true, constantValue: []V{V("99999")}},
|
|
|
|
"shadowwarning": {ldapField: "shadowWarning", notSearchable: true, constantValue: []V{V("7")}},
|
|
|
|
"shadowexpire": {ldapField: "shadowExpire", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
if user.IsForbidden {
|
|
|
|
return []V{V("1")}
|
|
|
|
} else {
|
|
|
|
return []V{V("-1")}
|
|
|
|
}
|
|
|
|
}},
|
|
|
|
"shadowinactive": {ldapField: "shadowInactive", notSearchable: true, constantValue: []V{V("0")}},
|
|
|
|
"shadowflag": {ldapField: "shadowFlag", notSearchable: true, constantValue: []V{V("0")}},
|
|
|
|
"memberof": {ldapField: "memberOf", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
|
|
|
var groupdn []V
|
|
|
|
for _, groupId := range user.Groups {
|
|
|
|
group, err := object.GetGroup(groupId)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("memberOf object.GetGroup error: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
groupdn = append(groupdn, V(fmt.Sprintf("cn=%s,cn=groups,ou=%s", group.Name, group.Owner)))
|
|
|
|
}
|
|
|
|
return groupdn
|
|
|
|
}},
|
|
|
|
"objectclass": {ldapField: "objectClass", notSearchable: true, constantValue: []V{
|
|
|
|
V("top"),
|
|
|
|
V("posixAccount"),
|
|
|
|
V("shadowAccount"),
|
|
|
|
V("person"),
|
|
|
|
V("organizationalPerson"),
|
|
|
|
V("inetOrgPerson"),
|
|
|
|
V("apple-user"),
|
|
|
|
V("sambaSamAccount"),
|
|
|
|
V("sambaIdmapEntry"),
|
|
|
|
V("extensibleObject"),
|
|
|
|
}},
|
2023-11-26 18:11:49 +03:00
|
|
|
}
|
|
|
|
|
2024-01-05 09:24:12 +08:00
|
|
|
var ldapGroupAttributesMapping = GroupFieldRelationMap{
|
|
|
|
"cn": {ldapField: "cn", hideOnStarOp: true, fieldMapper: func(group *object.Group) []V {
|
|
|
|
return []V{V(group.Name)}
|
|
|
|
}},
|
|
|
|
"gidnumber": {ldapField: "gidNumber", hideOnStarOp: true, fieldMapper: func(group *object.Group) []V {
|
|
|
|
return []V{V(fmt.Sprintf("%v", hash(group.Name)))}
|
|
|
|
}},
|
|
|
|
"member": {ldapField: "member", fieldMapper: func(group *object.Group) []V {
|
|
|
|
users, err := object.GetGroupUsers(group.GetId())
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("member object.GetGroupUsers error: %s", err)
|
|
|
|
return []V{V("")}
|
|
|
|
}
|
|
|
|
var members []V
|
|
|
|
for _, user := range users {
|
|
|
|
members = append(members, V(fmt.Sprintf("uid=%s,cn=users,ou=%s", user.Name, user.Owner)))
|
|
|
|
}
|
|
|
|
return members
|
|
|
|
}},
|
|
|
|
"memberuid": {ldapField: "memberUid", fieldMapper: func(group *object.Group) []V {
|
|
|
|
users, err := object.GetGroupUsers(group.GetId())
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("member object.GetGroupUsers error: %s", err)
|
|
|
|
return []V{V("")}
|
|
|
|
}
|
|
|
|
var members []message.AttributeValue
|
|
|
|
for _, user := range users {
|
|
|
|
members = append(members, message.AttributeValue(user.Name))
|
|
|
|
}
|
|
|
|
return members
|
|
|
|
}},
|
|
|
|
"description": {ldapField: "description", hideOnStarOp: true, fieldMapper: func(group *object.Group) []V {
|
|
|
|
return []V{V(group.DisplayName)}
|
|
|
|
}},
|
|
|
|
"objectclass": {ldapField: "objectClass", hideOnStarOp: true, constantValue: []V{
|
|
|
|
V("top"),
|
|
|
|
V("posixGroup"),
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
AdditionalLdapUserAttributes []message.LDAPString
|
|
|
|
AdditionalLdapGroupAttributes []message.LDAPString
|
|
|
|
)
|
2023-11-26 18:11:49 +03:00
|
|
|
|
|
|
|
func init() {
|
2024-01-05 09:24:12 +08:00
|
|
|
for _, v := range ldapUserAttributesMapping {
|
2023-11-26 18:11:49 +03:00
|
|
|
if v.hideOnStarOp {
|
|
|
|
continue
|
|
|
|
}
|
2024-01-05 09:24:12 +08:00
|
|
|
AdditionalLdapUserAttributes = append(AdditionalLdapUserAttributes, message.LDAPString(v.ldapField))
|
|
|
|
}
|
|
|
|
for _, v := range ldapGroupAttributesMapping {
|
|
|
|
if v.hideOnStarOp {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
AdditionalLdapGroupAttributes = append(AdditionalLdapGroupAttributes, message.LDAPString(v.ldapField))
|
2023-11-26 18:11:49 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-19 19:58:07 +08:00
|
|
|
func getNameAndOrgFromDN(DN string) (string, string, error) {
|
2023-04-04 20:51:28 +08:00
|
|
|
DNFields := strings.Split(DN, ",")
|
|
|
|
params := make(map[string]string, len(DNFields))
|
|
|
|
for _, field := range DNFields {
|
|
|
|
if strings.Contains(field, "=") {
|
|
|
|
k := strings.Split(field, "=")
|
|
|
|
params[k[0]] = k[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if params["cn"] == "" {
|
2023-11-19 19:58:07 +08:00
|
|
|
return "", "", fmt.Errorf("please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com")
|
2023-04-04 20:51:28 +08:00
|
|
|
}
|
|
|
|
if params["ou"] == "" {
|
2023-11-19 19:58:07 +08:00
|
|
|
return params["cn"], object.CasdoorOrganization, nil
|
2023-04-04 20:51:28 +08:00
|
|
|
}
|
2023-11-19 19:58:07 +08:00
|
|
|
return params["cn"], params["ou"], nil
|
2023-04-04 20:51:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
|
|
|
if !strings.Contains(baseDN, "ou=") {
|
|
|
|
return "", "", ldap.LDAPResultInvalidDNSyntax
|
|
|
|
}
|
|
|
|
|
2023-11-19 19:58:07 +08:00
|
|
|
name, org, err := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2023-04-04 20:51:28 +08:00
|
|
|
return name, org, ldap.LDAPResultSuccess
|
|
|
|
}
|
|
|
|
|
|
|
|
func getUsername(filter string) string {
|
|
|
|
nameIndex := strings.Index(filter, "cn=")
|
|
|
|
if nameIndex == -1 {
|
2023-05-06 23:31:46 +08:00
|
|
|
nameIndex = strings.Index(filter, "uid=")
|
|
|
|
if nameIndex == -1 {
|
|
|
|
return "*"
|
|
|
|
} else {
|
|
|
|
nameIndex += 4
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
nameIndex += 3
|
2023-04-04 20:51:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
var name string
|
2023-05-06 23:31:46 +08:00
|
|
|
for i := nameIndex; filter[i] != ')'; i++ {
|
2023-04-04 20:51:28 +08:00
|
|
|
name = name + string(filter[i])
|
|
|
|
}
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
2023-05-12 13:03:43 +08:00
|
|
|
func stringInSlice(value string, list []string) bool {
|
|
|
|
for _, item := range list {
|
|
|
|
if item == value {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-11-26 18:11:49 +03:00
|
|
|
func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
|
|
|
|
switch f := filter.(type) {
|
|
|
|
case message.FilterAnd:
|
|
|
|
conditions := make([]builder.Cond, len(f))
|
|
|
|
for i, v := range f {
|
|
|
|
cond, err := buildUserFilterCondition(v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
conditions[i] = cond
|
|
|
|
}
|
|
|
|
return builder.And(conditions...), nil
|
|
|
|
case message.FilterOr:
|
|
|
|
conditions := make([]builder.Cond, len(f))
|
|
|
|
for i, v := range f {
|
|
|
|
cond, err := buildUserFilterCondition(v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
conditions[i] = cond
|
|
|
|
}
|
|
|
|
return builder.Or(conditions...), nil
|
|
|
|
case message.FilterNot:
|
|
|
|
cond, err := buildUserFilterCondition(f.Filter)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return builder.Not{cond}, nil
|
|
|
|
case message.FilterEqualityMatch:
|
|
|
|
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return builder.Eq{field: string(f.AssertionValue())}, nil
|
|
|
|
case message.FilterPresent:
|
|
|
|
field, err := getUserFieldFromAttribute(string(f))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return builder.NotNull{field}, nil
|
|
|
|
case message.FilterGreaterOrEqual:
|
|
|
|
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return builder.Gte{field: string(f.AssertionValue())}, nil
|
|
|
|
case message.FilterLessOrEqual:
|
|
|
|
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return builder.Lte{field: string(f.AssertionValue())}, nil
|
|
|
|
case message.FilterSubstrings:
|
|
|
|
field, err := getUserFieldFromAttribute(string(f.Type_()))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var expr string
|
|
|
|
for _, substring := range f.Substrings() {
|
|
|
|
switch s := substring.(type) {
|
|
|
|
case message.SubstringInitial:
|
|
|
|
expr += string(s) + "%"
|
|
|
|
continue
|
|
|
|
case message.SubstringAny:
|
|
|
|
expr += string(s) + "%"
|
|
|
|
continue
|
|
|
|
case message.SubstringFinal:
|
|
|
|
expr += string(s)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return builder.Expr(field+" LIKE ?", expr), nil
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("LDAP filter operation %#v not supported", f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func buildSafeCondition(filter interface{}) builder.Cond {
|
|
|
|
condition, err := buildUserFilterCondition(filter)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("err = %v", err.Error())
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return condition
|
|
|
|
}
|
|
|
|
|
2023-04-04 20:51:28 +08:00
|
|
|
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
|
2023-05-30 15:49:39 +08:00
|
|
|
var err error
|
2023-04-04 20:51:28 +08:00
|
|
|
r := m.GetSearchRequest()
|
2023-04-13 14:12:31 +08:00
|
|
|
|
2023-04-04 20:51:28 +08:00
|
|
|
name, org, code := getNameAndOrgFromFilter(string(r.BaseObject()), r.FilterString())
|
|
|
|
if code != ldap.LDAPResultSuccess {
|
|
|
|
return nil, code
|
|
|
|
}
|
|
|
|
|
|
|
|
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
|
|
|
if m.Client.IsGlobalAdmin && org == "*" {
|
2023-11-26 18:11:49 +03:00
|
|
|
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
|
2023-05-30 15:49:39 +08:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2023-04-04 20:51:28 +08:00
|
|
|
return filteredUsers, ldap.LDAPResultSuccess
|
|
|
|
}
|
|
|
|
if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
|
2023-11-26 18:11:49 +03:00
|
|
|
filteredUsers, err = object.GetUsersWithFilter(org, buildSafeCondition(r.Filter()))
|
2023-05-30 15:49:39 +08:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2023-04-04 20:51:28 +08:00
|
|
|
return filteredUsers, ldap.LDAPResultSuccess
|
|
|
|
} else {
|
|
|
|
return nil, ldap.LDAPResultInsufficientAccessRights
|
|
|
|
}
|
|
|
|
} else {
|
2023-05-12 13:03:43 +08:00
|
|
|
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
|
|
|
|
userId := util.GetId(org, name)
|
|
|
|
|
|
|
|
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, "en")
|
2023-04-04 20:51:28 +08:00
|
|
|
if !hasPermission {
|
2023-09-25 20:55:02 +08:00
|
|
|
log.Printf("err = %v", err.Error())
|
2023-04-04 20:51:28 +08:00
|
|
|
return nil, ldap.LDAPResultInsufficientAccessRights
|
|
|
|
}
|
2023-05-12 13:03:43 +08:00
|
|
|
|
2023-05-30 15:49:39 +08:00
|
|
|
user, err := object.GetUser(userId)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2023-05-12 13:03:43 +08:00
|
|
|
if user != nil {
|
|
|
|
filteredUsers = append(filteredUsers, user)
|
|
|
|
return filteredUsers, ldap.LDAPResultSuccess
|
|
|
|
}
|
|
|
|
|
2023-05-30 15:49:39 +08:00
|
|
|
organization, err := object.GetOrganization(util.GetId("admin", org))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2023-05-12 13:03:43 +08:00
|
|
|
if organization == nil {
|
|
|
|
return nil, ldap.LDAPResultNoSuchObject
|
|
|
|
}
|
|
|
|
|
|
|
|
if !stringInSlice(name, organization.Tags) {
|
|
|
|
return nil, ldap.LDAPResultNoSuchObject
|
|
|
|
}
|
|
|
|
|
2023-11-26 18:11:49 +03:00
|
|
|
users, err := object.GetUsersByTagWithFilter(org, name, buildSafeCondition(r.Filter()))
|
2023-05-30 15:49:39 +08:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2023-05-12 13:03:43 +08:00
|
|
|
filteredUsers = append(filteredUsers, users...)
|
2023-04-04 20:51:28 +08:00
|
|
|
return filteredUsers, ldap.LDAPResultSuccess
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-05 09:24:12 +08:00
|
|
|
func GetFilteredOrganizations(m *ldap.Message) ([]*object.Organization, int) {
|
|
|
|
if m.Client.IsGlobalAdmin {
|
|
|
|
organizations, err := object.GetOrganizations("")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return organizations, ldap.LDAPResultSuccess
|
|
|
|
} else if m.Client.IsOrgAdmin {
|
|
|
|
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
|
|
|
|
user, err := object.GetUser(requestUserId)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
organization, err := object.GetOrganizationByUser(user)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return []*object.Organization{organization}, ldap.LDAPResultSuccess
|
|
|
|
} else {
|
|
|
|
return nil, ldap.LDAPResultInsufficientAccessRights
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetFilteredGroups(m *ldap.Message) ([]*object.Group, int) {
|
|
|
|
if m.Client.IsGlobalAdmin {
|
|
|
|
groups, err := object.GetGroups("")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return groups, ldap.LDAPResultSuccess
|
|
|
|
} else if m.Client.IsOrgAdmin {
|
|
|
|
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
|
|
|
|
user, err := object.GetUser(requestUserId)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
groups, err := object.GetGroups(user.Owner)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return groups, ldap.LDAPResultSuccess
|
|
|
|
} else {
|
|
|
|
return nil, ldap.LDAPResultInsufficientAccessRights
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-04 20:51:28 +08:00
|
|
|
// get user password with hash type prefix
|
|
|
|
// TODO not handle salt yet
|
|
|
|
// @return {md5}5f4dcc3b5aa765d61d8327deb882cf99
|
|
|
|
func getUserPasswordWithType(user *object.User) string {
|
2023-05-30 15:49:39 +08:00
|
|
|
org, err := object.GetOrganizationByUser(user)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2023-04-04 20:51:28 +08:00
|
|
|
if org.PasswordType == "" || org.PasswordType == "plain" {
|
|
|
|
return user.Password
|
|
|
|
}
|
|
|
|
prefix := org.PasswordType
|
|
|
|
if prefix == "salt" {
|
|
|
|
prefix = "sha256"
|
|
|
|
} else if prefix == "md5-salt" {
|
|
|
|
prefix = "md5"
|
|
|
|
} else if prefix == "pbkdf2-salt" {
|
|
|
|
prefix = "pbkdf2"
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
|
|
|
}
|
2023-04-13 14:12:31 +08:00
|
|
|
|
2023-11-26 18:11:49 +03:00
|
|
|
func getUserFieldFromAttribute(attributeName string) (string, error) {
|
2024-01-05 09:24:12 +08:00
|
|
|
v, ok := ldapUserAttributesMapping.CaseInsensitiveGet(attributeName)
|
2023-11-26 18:11:49 +03:00
|
|
|
if !ok {
|
|
|
|
return "", fmt.Errorf("attribute %s not supported", attributeName)
|
|
|
|
}
|
|
|
|
return v.GetField()
|
2023-04-13 14:12:31 +08:00
|
|
|
}
|
2024-01-05 09:24:12 +08:00
|
|
|
|
|
|
|
func searchFilterForEquality(filter message.Filter, desc string, values ...string) string {
|
|
|
|
switch f := filter.(type) {
|
|
|
|
case message.FilterAnd:
|
|
|
|
for _, child := range f {
|
|
|
|
if val := searchFilterForEquality(child, desc, values...); val != "" {
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case message.FilterOr:
|
|
|
|
for _, child := range f {
|
|
|
|
if val := searchFilterForEquality(child, desc, values...); val != "" {
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case message.FilterNot:
|
|
|
|
return searchFilterForEquality(f.Filter, desc, values...)
|
|
|
|
case message.FilterSubstrings:
|
|
|
|
// Handle FilterSubstrings case if needed
|
|
|
|
case message.FilterEqualityMatch:
|
|
|
|
if strings.EqualFold(string(f.AttributeDesc()), desc) {
|
|
|
|
for _, value := range values {
|
|
|
|
if val := string(f.AssertionValue()); val == value {
|
|
|
|
return val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
case message.FilterGreaterOrEqual:
|
|
|
|
// Handle FilterGreaterOrEqual case if needed
|
|
|
|
case message.FilterLessOrEqual:
|
|
|
|
// Handle FilterLessOrEqual case if needed
|
|
|
|
case message.FilterPresent:
|
|
|
|
// Handle FilterPresent case if needed
|
|
|
|
case message.FilterApproxMatch:
|
|
|
|
// Handle FilterApproxMatch case if needed
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|