123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- // Copyright (c) 2020 Matt Ouille
- // Copyright (c) 2020 Shivaram Lingamneni
- // released under the MIT license
-
- // Portions of this code copyright Grafana Labs and contributors
- // and released under the Apache 2.0 license
-
- // Copying Grafana's original comment on the different cases for LDAP:
- // There are several cases -
- // 1. "admin" user
- // Bind the "admin" user (defined in Grafana config file) which has the search privileges
- // in LDAP server, then we search the targeted user through that bind, then the second
- // perform the bind via passed login/password.
- // 2. Single bind
- // // If all the users meant to be used with Grafana have the ability to search in LDAP server
- // then we bind with LDAP server with targeted login/password
- // and then search for the said user in order to retrive all the information about them
- // 3. Unauthenticated bind
- // For some LDAP configurations it is allowed to search the
- // user without login/password binding with LDAP server, in such case
- // we will perform "unauthenticated bind", then search for the
- // targeted user and then perform the bind with passed login/password.
-
- // Note: the only validation we do on users is to check RequiredGroups.
- // If RequiredGroups is not set and we can do a single bind, we don't
- // even need to search. So our case 2 is not restricted
- // to setups where all the users have search privileges: we only need to
- // be able to do DN resolution via pure string substitution.
-
- package ldap
-
- import (
- "errors"
- "fmt"
-
- ldap "github.com/go-ldap/ldap/v3"
-
- "github.com/oragono/oragono/irc/logger"
- )
-
- var (
- ErrUserNotInRequiredGroup = errors.New("User is not a member of any required groups")
- )
-
- // equivalent of Grafana's `Server`, but unexported
- // also, `log` was renamed to `logger`, since the APIs are slightly different
- // and this way the compiler will catch any unchanged references to Grafana's `Server.log`
- type serverConn struct {
- Config *ServerConfig
- Connection *ldap.Conn
- logger *logger.Manager
- }
-
- func CheckLDAPPassphrase(config ServerConfig, accountName, passphrase string, log *logger.Manager) (err error) {
- defer func() {
- if err != nil {
- log.Debug("ldap", "failed passphrase check", err.Error())
- }
- }()
-
- server := serverConn{
- Config: &config,
- logger: log,
- }
-
- err = server.Dial()
- if err != nil {
- return
- }
- defer server.Close()
-
- server.Connection.SetTimeout(config.Timeout)
-
- passphraseChecked := false
-
- if server.shouldSingleBind() {
- log.Debug("ldap", "attempting single bind to", accountName)
- err = server.userBind(server.singleBindDN(accountName), passphrase)
- passphraseChecked = (err == nil)
- } else if server.shouldAdminBind() {
- log.Debug("ldap", "attempting admin bind to", config.BindDN)
- err = server.userBind(config.BindDN, config.BindPassword)
- } else {
- log.Debug("ldap", "attempting unauthenticated bind")
- err = server.Connection.UnauthenticatedBind(config.BindDN)
- }
-
- if err != nil {
- return
- }
-
- if passphraseChecked && len(config.RequireGroups) == 0 {
- return nil
- }
-
- users, err := server.users([]string{accountName})
- if err != nil {
- log.Debug("ldap", "failed user lookup")
- return err
- }
-
- if len(users) == 0 {
- return ErrCouldNotFindUser
- }
-
- user := users[0]
-
- log.Debug("ldap", "looked up user", user.DN)
-
- err = server.validateGroupMembership(user)
- if err != nil {
- return err
- }
-
- if !passphraseChecked {
- log.Debug("ldap", "rebinding", user.DN)
- err = server.userBind(user.DN, passphrase)
- }
-
- return err
- }
-
- func (server *serverConn) validateGroupMembership(user *ldap.Entry) (err error) {
- if len(server.Config.RequireGroups) == 0 {
- return
- }
-
- var memberOf []string
- memberOf, err = server.getMemberOf(user)
- if err != nil {
- server.logger.Debug("ldap", "could not retrieve group memberships", err.Error())
- return
- }
- server.logger.Debug("ldap", fmt.Sprintf("found group memberships: %v", memberOf))
- foundGroup := false
- for _, inGroup := range memberOf {
- for _, acceptableGroup := range server.Config.RequireGroups {
- if inGroup == acceptableGroup {
- foundGroup = true
- break
- }
- }
- if foundGroup {
- break
- }
- }
- if foundGroup {
- return nil
- } else {
- return ErrUserNotInRequiredGroup
- }
- }
|