github.com/cs3org/reva/v2@v2.27.7/pkg/group/manager/ldap/ldap.go (about) 1 // Copyright 2018-2020 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package ldap 20 21 import ( 22 "context" 23 "fmt" 24 "strconv" 25 26 grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" 27 userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 28 "github.com/cs3org/reva/v2/pkg/appctx" 29 "github.com/cs3org/reva/v2/pkg/errtypes" 30 "github.com/cs3org/reva/v2/pkg/group" 31 "github.com/cs3org/reva/v2/pkg/group/manager/registry" 32 "github.com/cs3org/reva/v2/pkg/utils" 33 ldapIdentity "github.com/cs3org/reva/v2/pkg/utils/ldap" 34 "github.com/go-ldap/ldap/v3" 35 "github.com/google/uuid" 36 "github.com/mitchellh/mapstructure" 37 "github.com/pkg/errors" 38 ) 39 40 func init() { 41 registry.Register("ldap", New) 42 } 43 44 type manager struct { 45 c *config 46 ldapClient ldap.Client 47 } 48 49 type config struct { 50 utils.LDAPConn `mapstructure:",squash"` 51 LDAPIdentity ldapIdentity.Identity `mapstructure:",squash"` 52 Idp string `mapstructure:"idp"` 53 // Nobody specifies the fallback gid number for groups that don't have a gidNumber set in LDAP 54 Nobody int64 `mapstructure:"nobody"` 55 } 56 57 func parseConfig(m map[string]interface{}) (*config, error) { 58 c := config{ 59 LDAPIdentity: ldapIdentity.New(), 60 } 61 if err := mapstructure.Decode(m, &c); err != nil { 62 err = errors.Wrap(err, "error decoding conf") 63 return nil, err 64 } 65 66 return &c, nil 67 } 68 69 // New returns a group manager implementation that connects to a LDAP server to provide group metadata. 70 func New(m map[string]interface{}) (group.Manager, error) { 71 mgr := &manager{} 72 err := mgr.Configure(m) 73 if err != nil { 74 return nil, err 75 } 76 77 mgr.ldapClient, err = utils.GetLDAPClientWithReconnect(&mgr.c.LDAPConn) 78 if err != nil { 79 return nil, err 80 } 81 return mgr, nil 82 } 83 84 // Configure initializes the configuration of the group manager from the supplied config map 85 func (m *manager) Configure(ml map[string]interface{}) error { 86 c, err := parseConfig(ml) 87 if err != nil { 88 return err 89 } 90 if c.Nobody == 0 { 91 c.Nobody = 99 92 } 93 94 if err = c.LDAPIdentity.Setup(); err != nil { 95 return fmt.Errorf("error setting up Identity config: %w", err) 96 } 97 m.c = c 98 return nil 99 } 100 101 // GetGroup implements the group.Manager interface. Looks up a group by Id and return the group 102 func (m *manager) GetGroup(ctx context.Context, gid *grouppb.GroupId, skipFetchingMembers bool) (*grouppb.Group, error) { 103 log := appctx.GetLogger(ctx) 104 if gid.Idp != "" && gid.Idp != m.c.Idp { 105 return nil, errtypes.NotFound("idp mismatch") 106 } 107 108 groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByID(log, m.ldapClient, gid.OpaqueId) 109 if err != nil { 110 return nil, err 111 } 112 113 log.Debug().Interface("entry", groupEntry).Msg("entries") 114 115 g, err := m.ldapEntryToGroup(groupEntry) 116 if err != nil { 117 return nil, err 118 } 119 120 if skipFetchingMembers { 121 return g, nil 122 } 123 124 members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(log, m.ldapClient, groupEntry) 125 if err != nil { 126 return nil, err 127 } 128 129 memberIDs := make([]*userpb.UserId, 0, len(members)) 130 for _, member := range members { 131 userid, err := m.ldapEntryToUserID(member) 132 if err != nil { 133 log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid") 134 continue 135 } 136 memberIDs = append(memberIDs, userid) 137 } 138 139 g.Members = memberIDs 140 141 return g, nil 142 } 143 144 // GetGroupByClaim implements the group.Manager interface. Looks up a group by 145 // claim ('group_name', 'group_id', 'display_name') and returns the group. 146 func (m *manager) GetGroupByClaim(ctx context.Context, claim, value string, skipFetchingMembers bool) (*grouppb.Group, error) { 147 log := appctx.GetLogger(ctx) 148 groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByAttribute(log, m.ldapClient, claim, value) 149 if err != nil { 150 log.Debug().Err(err).Msg("GetGroupByClaim") 151 return nil, err 152 } 153 154 log.Debug().Interface("entry", groupEntry).Msg("entries") 155 156 g, err := m.ldapEntryToGroup(groupEntry) 157 if err != nil { 158 return nil, err 159 } 160 161 if skipFetchingMembers { 162 return g, nil 163 } 164 165 members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(log, m.ldapClient, groupEntry) 166 if err != nil { 167 return nil, err 168 } 169 170 memberIDs := make([]*userpb.UserId, 0, len(members)) 171 for _, member := range members { 172 userid, err := m.ldapEntryToUserID(member) 173 if err != nil { 174 log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid") 175 continue 176 } 177 memberIDs = append(memberIDs, userid) 178 } 179 180 g.Members = memberIDs 181 182 return g, nil 183 } 184 185 // FindGroups implements the group.Manager interface. Searches for groups using 186 // a prefix-substring search on the group attributes ('group_name', 187 // 'display_name', 'group_id') and returns the groups. FindGroups does NOT expand the 188 // members of the Groups. 189 func (m *manager) FindGroups(ctx context.Context, query string, skipFetchingMembers bool) ([]*grouppb.Group, error) { 190 log := appctx.GetLogger(ctx) 191 entries, err := m.c.LDAPIdentity.GetLDAPGroups(log, m.ldapClient, query) 192 if err != nil { 193 return nil, err 194 } 195 196 groups := make([]*grouppb.Group, 0, len(entries)) 197 198 for _, entry := range entries { 199 g, err := m.ldapEntryToGroup(entry) 200 if err != nil { 201 return nil, err 202 } 203 204 groups = append(groups, g) 205 } 206 207 return groups, nil 208 } 209 210 // GetMembers implements the group.Manager interface. It returns all the userids of the members 211 // of the group identified by the supplied id. 212 func (m *manager) GetMembers(ctx context.Context, gid *grouppb.GroupId) ([]*userpb.UserId, error) { 213 log := appctx.GetLogger(ctx) 214 if gid.Idp != "" && gid.Idp != m.c.Idp { 215 return nil, errtypes.NotFound("idp mismatch") 216 } 217 218 groupEntry, err := m.c.LDAPIdentity.GetLDAPGroupByID(log, m.ldapClient, gid.OpaqueId) 219 if err != nil { 220 return nil, err 221 } 222 223 log.Debug().Interface("entry", groupEntry).Msg("entries") 224 225 members, err := m.c.LDAPIdentity.GetLDAPGroupMembers(log, m.ldapClient, groupEntry) 226 if err != nil { 227 return nil, err 228 } 229 230 memberIDs := make([]*userpb.UserId, 0, len(members)) 231 for _, member := range members { 232 userid, err := m.ldapEntryToUserID(member) 233 if err != nil { 234 log.Warn().Err(err).Interface("member", member).Msg("Failed convert member entry to userid") 235 continue 236 } 237 memberIDs = append(memberIDs, userid) 238 } 239 240 return memberIDs, nil 241 } 242 243 // HasMember implements the group.Member interface. Checks whether the supplied userid is a member 244 // of the supplied groupid. 245 func (m *manager) HasMember(ctx context.Context, gid *grouppb.GroupId, uid *userpb.UserId) (bool, error) { 246 // It might be possible to do a somewhat more clever LDAP search here. (First lookup the user and then 247 // search for (&(objectclass=<groupoc>)(<groupid>=gid)(member=<username/userdn>) 248 // The GetMembers call used below can be quiet ineffecient for large groups 249 members, err := m.GetMembers(ctx, gid) 250 if err != nil { 251 return false, err 252 } 253 254 for _, u := range members { 255 if u.OpaqueId == uid.OpaqueId && u.Idp == uid.Idp { 256 return true, nil 257 } 258 } 259 return false, nil 260 } 261 262 func (m *manager) ldapEntryToGroup(entry *ldap.Entry) (*grouppb.Group, error) { 263 id, err := m.ldapEntryToGroupID(entry) 264 if err != nil { 265 return nil, err 266 } 267 268 gidNumber := m.c.Nobody 269 gidValue := entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.GIDNumber) 270 if gidValue != "" { 271 gidNumber, err = strconv.ParseInt(gidValue, 10, 64) 272 if err != nil { 273 return nil, err 274 } 275 } 276 277 g := &grouppb.Group{ 278 Id: id, 279 GroupName: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.Groupname), 280 Mail: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.Mail), 281 DisplayName: entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.DisplayName), 282 GidNumber: gidNumber, 283 } 284 285 return g, nil 286 } 287 288 func (m *manager) ldapEntryToGroupID(entry *ldap.Entry) (*grouppb.GroupId, error) { 289 var id string 290 if m.c.LDAPIdentity.Group.Schema.IDIsOctetString { 291 rawValue := entry.GetEqualFoldRawAttributeValue(m.c.LDAPIdentity.Group.Schema.ID) 292 if value, err := uuid.FromBytes(rawValue); err == nil { 293 id = value.String() 294 } else { 295 return nil, err 296 } 297 } else { 298 id = entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.Group.Schema.ID) 299 } 300 301 return &grouppb.GroupId{ 302 Idp: m.c.Idp, 303 OpaqueId: id, 304 }, nil 305 } 306 307 func (m *manager) ldapEntryToUserID(entry *ldap.Entry) (*userpb.UserId, error) { 308 var uid string 309 if m.c.LDAPIdentity.User.Schema.IDIsOctetString { 310 rawValue := entry.GetEqualFoldRawAttributeValue(m.c.LDAPIdentity.User.Schema.ID) 311 var value uuid.UUID 312 var err error 313 if value, err = uuid.FromBytes(rawValue); err != nil { 314 return nil, err 315 } 316 uid = value.String() 317 } else { 318 uid = entry.GetEqualFoldAttributeValue(m.c.LDAPIdentity.User.Schema.ID) 319 } 320 321 return &userpb.UserId{ 322 Idp: m.c.Idp, 323 OpaqueId: uid, 324 Type: userpb.UserType_USER_TYPE_PRIMARY, 325 }, nil 326 }