github.com/cs3org/reva/v2@v2.27.7/pkg/auth/manager/ldap/ldap.go (about) 1 // Copyright 2018-2021 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 authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" 27 user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" 28 rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" 29 "github.com/cs3org/reva/v2/pkg/appctx" 30 "github.com/cs3org/reva/v2/pkg/auth" 31 "github.com/cs3org/reva/v2/pkg/auth/manager/registry" 32 "github.com/cs3org/reva/v2/pkg/auth/scope" 33 "github.com/cs3org/reva/v2/pkg/errtypes" 34 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 35 "github.com/cs3org/reva/v2/pkg/sharedconf" 36 "github.com/cs3org/reva/v2/pkg/utils" 37 ldapIdentity "github.com/cs3org/reva/v2/pkg/utils/ldap" 38 "github.com/go-ldap/ldap/v3" 39 "github.com/google/uuid" 40 "github.com/mitchellh/mapstructure" 41 "github.com/pkg/errors" 42 ) 43 44 func init() { 45 registry.Register("ldap", New) 46 } 47 48 type mgr struct { 49 c *config 50 ldapClient ldap.Client 51 } 52 53 type config struct { 54 utils.LDAPConn `mapstructure:",squash"` 55 LDAPIdentity ldapIdentity.Identity `mapstructure:",squash"` 56 Idp string `mapstructure:"idp"` 57 GatewaySvc string `mapstructure:"gatewaysvc"` 58 Nobody int64 `mapstructure:"nobody"` 59 LoginAttributes []string `mapstructure:"login_attributes"` 60 } 61 62 func parseConfig(m map[string]interface{}) (*config, error) { 63 c := &config{ 64 LDAPIdentity: ldapIdentity.New(), 65 LoginAttributes: []string{"cn"}, 66 } 67 if err := mapstructure.Decode(m, c); err != nil { 68 err = errors.Wrap(err, "error decoding conf") 69 return nil, err 70 } 71 return c, nil 72 } 73 74 // New returns an auth manager implementation that connects to a LDAP server to validate the user. 75 func New(m map[string]interface{}) (auth.Manager, error) { 76 manager := &mgr{} 77 err := manager.Configure(m) 78 if err != nil { 79 return nil, err 80 } 81 manager.ldapClient, err = utils.GetLDAPClientWithReconnect(&manager.c.LDAPConn) 82 if err != nil { 83 return nil, err 84 } 85 return manager, nil 86 } 87 88 func (am *mgr) Configure(m map[string]interface{}) error { 89 c, err := parseConfig(m) 90 if err != nil { 91 return err 92 } 93 94 if c.Nobody == 0 { 95 c.Nobody = 99 96 } 97 98 if err = c.LDAPIdentity.Setup(); err != nil { 99 return fmt.Errorf("error setting up Identity config: %w", err) 100 } 101 c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) 102 am.c = c 103 return nil 104 } 105 106 func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) { 107 log := appctx.GetLogger(ctx) 108 109 filter := am.getLoginFilter(clientID) 110 111 userEntry, err := am.c.LDAPIdentity.GetLDAPUserByFilter(log, am.ldapClient, filter) 112 113 if err != nil { 114 return nil, nil, err 115 } 116 117 // Bind as the user to verify their password 118 la, err := utils.GetLDAPClientForAuth(&am.c.LDAPConn) 119 if err != nil { 120 return nil, nil, err 121 } 122 defer la.Close() 123 err = la.Bind(userEntry.DN, clientSecret) 124 switch { 125 case err == nil: 126 break 127 case ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials): 128 return nil, nil, errtypes.InvalidCredentials(clientID) 129 default: 130 log.Debug().Err(err).Interface("userdn", userEntry.DN).Msg("bind with user credentials failed") 131 return nil, nil, err 132 } 133 134 var uid string 135 if am.c.LDAPIdentity.User.Schema.IDIsOctetString { 136 rawValue := userEntry.GetEqualFoldRawAttributeValue(am.c.LDAPIdentity.User.Schema.ID) 137 if value, err := uuid.FromBytes(rawValue); err == nil { 138 uid = value.String() 139 } 140 } else { 141 uid = userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.ID) 142 } 143 144 userID := &user.UserId{ 145 Idp: am.c.Idp, 146 OpaqueId: uid, 147 Type: am.c.LDAPIdentity.GetUserType(userEntry), 148 } 149 gwc, err := pool.GetGatewayServiceClient(am.c.GatewaySvc) 150 if err != nil { 151 return nil, nil, errors.Wrap(err, "ldap: error getting gateway grpc client") 152 } 153 getGroupsResp, err := gwc.GetUserGroups(ctx, &user.GetUserGroupsRequest{ 154 UserId: userID, 155 }) 156 if err != nil { 157 log.Warn().Err(err).Msg("error getting user groups") 158 return nil, nil, errors.Wrap(err, "ldap: error getting user groups") 159 } 160 if getGroupsResp.Status.Code != rpc.Code_CODE_OK { 161 log.Warn().Err(err).Str("msg", getGroupsResp.Status.Message).Msg("grpc getting user groups failed") 162 return nil, nil, fmt.Errorf("ldap: grpc getting user groups failed: '%s'", getGroupsResp.Status.Message) 163 } 164 gidNumber := am.c.Nobody 165 gidValue := userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.GIDNumber) 166 if gidValue != "" { 167 gidNumber, err = strconv.ParseInt(gidValue, 10, 64) 168 if err != nil { 169 return nil, nil, err 170 } 171 } 172 uidNumber := am.c.Nobody 173 uidValue := userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.UIDNumber) 174 if uidValue != "" { 175 uidNumber, err = strconv.ParseInt(uidValue, 10, 64) 176 if err != nil { 177 return nil, nil, err 178 } 179 } 180 u := &user.User{ 181 Id: userID, 182 // TODO add more claims from the StandardClaims, eg EmailVerified 183 Username: userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.Username), 184 // TODO groups 185 Groups: getGroupsResp.Groups, 186 Mail: userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.Mail), 187 DisplayName: userEntry.GetEqualFoldAttributeValue(am.c.LDAPIdentity.User.Schema.DisplayName), 188 UidNumber: uidNumber, 189 GidNumber: gidNumber, 190 } 191 192 var scopes map[string]*authpb.Scope 193 if userID != nil && userID.Type == user.UserType_USER_TYPE_LIGHTWEIGHT { 194 scopes, err = scope.AddLightweightAccountScope(authpb.Role_ROLE_OWNER, nil) 195 if err != nil { 196 return nil, nil, err 197 } 198 } else { 199 scopes, err = scope.AddOwnerScope(nil) 200 if err != nil { 201 return nil, nil, err 202 } 203 } 204 205 log.Debug().Interface("entry", userEntry).Interface("user", u).Msg("authenticated user") 206 207 return u, scopes, nil 208 } 209 210 func (am *mgr) getLoginFilter(login string) string { 211 var filter string 212 for _, attr := range am.c.LoginAttributes { 213 filter = fmt.Sprintf("%s(%s=%s)", filter, attr, ldap.EscapeFilter(login)) 214 } 215 216 return fmt.Sprintf("(&%s(objectclass=%s)(|%s))", 217 am.c.LDAPIdentity.User.Filter, 218 am.c.LDAPIdentity.User.Objectclass, 219 filter, 220 ) 221 }