summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlexander Sulfrian <alexander@sulfrian.net>2018-10-27 10:20:02 +0200
committerAlexander Sulfrian <asulfrian@zedat.fu-berlin.de>2020-01-07 18:41:37 +0100
commit9db4666a657bf137b371b9163a60e9c818ea31f3 (patch)
treed9dd69aea7e9443b46387aae5bfb4efbd41a65c2
parent937b6480d534b3051cadf4a892a9aa210bec579a (diff)
downloadchat-9db4666a657bf137b371b9163a60e9c818ea31f3.zip
chat-9db4666a657bf137b371b9163a60e9c818ea31f3.tar.gz
chat-9db4666a657bf137b371b9163a60e9c818ea31f3.tar.bz2
ldap: Add own ldap authenticationspline
-rw-r--r--app/authentication.go3
-rw-r--r--app/ldap.go5
-rw-r--r--app/license.go5
-rw-r--r--model/ldap/ldap.go413
-rw-r--r--utils/config.go24
-rw-r--r--utils/license.go4
6 files changed, 429 insertions, 25 deletions
diff --git a/app/authentication.go b/app/authentication.go
index 566eac8..ef200ed 100644
--- a/app/authentication.go
+++ b/app/authentication.go
@@ -181,8 +181,7 @@ func checkUserNotDisabled(user *model.User) *model.AppError {
}
func (a *App) authenticateUser(user *model.User, password, mfaToken string) (*model.User, *model.AppError) {
- license := a.License()
- ldapAvailable := *a.Config().LdapSettings.Enable && a.Ldap != nil && license != nil && *license.Features.LDAP
+ ldapAvailable := *a.Config().LdapSettings.Enable && a.Ldap != nil
if user.AuthService == model.USER_AUTH_SERVICE_LDAP {
if !ldapAvailable {
diff --git a/app/ldap.go b/app/ldap.go
index 544905b..a737602 100644
--- a/app/ldap.go
+++ b/app/ldap.go
@@ -15,7 +15,7 @@ import (
func (a *App) SyncLdap() {
a.Go(func() {
- if license := a.License(); license != nil && *license.Features.LDAP && *a.Config().LdapSettings.EnableSync {
+ if *a.Config().LdapSettings.EnableSync {
if ldapI := a.Ldap; ldapI != nil {
ldapI.StartSynchronizeJob(false)
} else {
@@ -26,8 +26,7 @@ func (a *App) SyncLdap() {
}
func (a *App) TestLdap() *model.AppError {
- license := a.License()
- if ldapI := a.Ldap; ldapI != nil && license != nil && *license.Features.LDAP && (*a.Config().LdapSettings.Enable || *a.Config().LdapSettings.EnableSync) {
+ if ldapI := a.Ldap; ldapI != nil && (*a.Config().LdapSettings.Enable || *a.Config().LdapSettings.EnableSync) {
if err := ldapI.RunTest(); err != nil {
err.StatusCode = 500
return err
diff --git a/app/license.go b/app/license.go
index ec18ec3..d59fd01 100644
--- a/app/license.go
+++ b/app/license.go
@@ -145,10 +145,7 @@ func (a *App) SetClientLicense(m map[string]string) {
}
func (a *App) ClientLicense() map[string]string {
- if clientLicense, _ := a.clientLicenseValue.Load().(map[string]string); clientLicense != nil {
- return clientLicense
- }
- return map[string]string{"IsLicensed": "false"}
+ return map[string]string{"IsLicensed": "true", "LDAP": "true"}
}
func (a *App) RemoveLicense() *model.AppError {
diff --git a/model/ldap/ldap.go b/model/ldap/ldap.go
new file mode 100644
index 0000000..5f116f0
--- /dev/null
+++ b/model/ldap/ldap.go
@@ -0,0 +1,413 @@
+package ldapauth
+
+import (
+ "crypto/tls"
+ "fmt"
+ "strconv"
+ "time"
+ "net/http"
+
+ "github.com/mattermost/mattermost-server/app"
+ "github.com/mattermost/mattermost-server/einterfaces"
+ "github.com/mattermost/mattermost-server/model"
+ "github.com/mattermost/mattermost-server/store"
+ "gopkg.in/ldap.v2"
+)
+
+func init() {
+ app.RegisterLdapInterface(NewLdapInterface)
+}
+
+
+type LdapConnection struct {
+ Settings *model.LdapSettings
+ Ldap *ldap.Conn
+}
+
+func NewLdapConnection(settings *model.LdapSettings) (*LdapConnection, *model.AppError) {
+ c := new(LdapConnection)
+ c.Settings = settings
+
+ addr := fmt.Sprintf("%s:%d", *c.Settings.LdapServer, *c.Settings.LdapPort)
+ tlsConfig := &tls.Config{InsecureSkipVerify: *c.Settings.SkipCertificateVerification}
+
+ if *c.Settings.ConnectionSecurity == "TLS" {
+ ldapConn, err := ldap.DialTLS("tcp", addr, tlsConfig)
+ if err != nil {
+ return nil, model.NewAppError("NewLdapConnection", "ent.ldap.do_login.unable_to_connect.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ c.Ldap = ldapConn
+ } else {
+ ldapConn, err := ldap.Dial("tcp", addr)
+ if err != nil {
+ return nil, model.NewAppError("NewLdapConnection", "ent.ldap.do_login.unable_to_connect.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if *c.Settings.ConnectionSecurity == "STARTTLS" {
+ if err := ldapConn.StartTLS(tlsConfig); err != nil {
+ return nil, model.NewAppError("NewLdapConnection", "ent.ldap.do_login.unable_to_connect.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+ }
+
+ c.Ldap = ldapConn
+ }
+
+ c.Ldap.SetTimeout(time.Duration(*c.Settings.QueryTimeout) * time.Second)
+
+ if len(*c.Settings.BindUsername) > 0 {
+ if err := c.Ldap.Bind(*c.Settings.BindUsername, *c.Settings.BindPassword); err != nil {
+ return nil, model.NewAppError("NewLdapConnection", "ent.ldap.do_login.bind_admin_user.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+ }
+
+ return c, nil
+}
+
+func (c LdapConnection) Close() {
+ c.Ldap.Close()
+}
+
+func (c LdapConnection) Search(filter string, attributes []string) (map[string]string, *model.AppError) {
+ searchRequest := ldap.NewSearchRequest(
+ *c.Settings.BaseDN,
+ ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+ filter, attributes,
+ nil,
+ )
+
+ result, err := c.Ldap.Search(searchRequest)
+ if err != nil {
+ return nil, model.NewAppError("Search", "ent.ldap.do_login.search_ldap_server.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ if len(result.Entries) == 0 {
+ return nil, model.NewAppError("Search", "ent.ldap.do_login.user_not_registered.app_error", nil, "filter="+filter, http.StatusUnauthorized)
+ } else if len(result.Entries) > 1 {
+ return nil, model.NewAppError("Search", "ent.ldap.do_login.matched_to_many_users.app_error", nil, "filter="+filter, http.StatusBadRequest)
+ }
+
+ values := map[string]string{}
+ for _, attribute := range attributes {
+ values[attribute] = result.Entries[0].GetAttributeValue(attribute)
+ }
+
+ return values, nil
+}
+
+func (c LdapConnection) FindAuthData(id string) (string, *model.AppError) {
+ values, err := c.GetUserAttributes(id, []string{*c.Settings.IdAttribute})
+ if err != nil {
+ return "", err
+ }
+
+ authData := values[*c.Settings.IdAttribute]
+ if len(authData) == 0 {
+ return "", model.NewAppError("FindUser", "ent.ldap.do_login.search_ldap_server.app_error", nil, "userId="+id, http.StatusBadRequest)
+ }
+
+ return authData, nil
+}
+
+func addAttribute(attributes []string, attr string) []string {
+ if len(attr) == 0 {
+ return attributes
+ }
+
+ for _, attribute := range attributes {
+ if attribute == attr {
+ return attributes
+ }
+ }
+
+ return append(attributes, attr)
+}
+
+func (c LdapConnection) FindUser(id string) (*model.User, *model.AppError) {
+ attributes := []string{*c.Settings.IdAttribute, *c.Settings.UsernameAttribute, *c.Settings.EmailAttribute}
+ attributes = addAttribute(attributes, *c.Settings.FirstNameAttribute)
+ attributes = addAttribute(attributes, *c.Settings.LastNameAttribute)
+ attributes = addAttribute(attributes, *c.Settings.NicknameAttribute)
+ attributes = addAttribute(attributes, *c.Settings.PositionAttribute)
+
+ attrs, err := c.GetUserAttributes(id, attributes)
+ if err != nil {
+ return nil, err
+ }
+
+ user := &model.User{}
+ user.Username = attrs[*c.Settings.UsernameAttribute]
+ user.Email = attrs[*c.Settings.EmailAttribute]
+ authData := attrs[*c.Settings.IdAttribute]
+ user.AuthData = &authData
+
+ /* optional user attributes */
+ if len(*c.Settings.FirstNameAttribute) > 0 {
+ user.FirstName = attrs[*c.Settings.FirstNameAttribute]
+ }
+
+ if len(*c.Settings.LastNameAttribute) > 0 {
+ user.LastName = attrs[*c.Settings.LastNameAttribute]
+ }
+
+ if len(*c.Settings.NicknameAttribute) > 0 {
+ user.Nickname = attrs[*c.Settings.NicknameAttribute]
+ }
+
+ if len(*c.Settings.PositionAttribute) > 0 {
+ user.Position = attrs[*c.Settings.PositionAttribute]
+ }
+
+ return user, nil
+}
+
+func (c LdapConnection) GetUserAttributes(id string, attributes []string) (map[string]string, *model.AppError) {
+ loginIdAttribute := *c.Settings.LoginIdAttribute
+ if len(loginIdAttribute) == 0 {
+ loginIdAttribute = *c.Settings.UsernameAttribute
+ }
+
+ filter := fmt.Sprintf("(%s=%s)", loginIdAttribute, ldap.EscapeFilter(id))
+ if len(*c.Settings.UserFilter) > 0 {
+ filter = fmt.Sprintf("(&%s%s)", filter, *c.Settings.UserFilter)
+ }
+
+ values, err := c.Search(filter, attributes)
+ if err != nil {
+ return nil, err
+ }
+
+ return values, nil
+}
+
+func (c LdapConnection) CheckPassword(authData string, password string) *model.AppError {
+ if err := c.Ldap.Bind(authData, password); err != nil {
+ return model.NewAppError("CheckPasswordAuthData", "ent.ldap.do_login.invalid_password.app_error", nil, "auth_data="+authData, http.StatusUnauthorized)
+ }
+
+ return nil
+}
+
+type LdapInterface struct {
+ App *app.App;
+}
+
+func NewLdapInterface(app *app.App) einterfaces.LdapInterface {
+ return LdapInterface{App: app}
+}
+
+func (i LdapInterface) NewLdapConnection() (*LdapConnection, *model.AppError) {
+ settings := &i.App.GetConfig().LdapSettings
+ return NewLdapConnection(settings)
+}
+
+func (i LdapInterface) DoLogin(id string, password string) (*model.User, *model.AppError) {
+ c, err := i.NewLdapConnection()
+ if err != nil {
+ return nil, err
+ }
+ defer c.Close()
+
+ ldapUser, err := c.FindUser(id)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := c.CheckPassword(*ldapUser.AuthData, password); err != nil {
+ return nil, err
+ }
+
+ return i.GetLdapUser(ldapUser)
+}
+
+func (i LdapInterface) GetUser(id string) (*model.User, *model.AppError) {
+ c, err := i.NewLdapConnection()
+ if err != nil {
+ return nil, err
+ }
+ defer c.Close()
+
+ ldapUser, err := c.FindUser(id)
+ if err != nil {
+ return nil, err
+ }
+
+ return i.GetLdapUser(ldapUser)
+}
+
+func (i LdapInterface) GetUserAttributes(id string, attributes []string) (map[string]string, *model.AppError) {
+ c, err := i.NewLdapConnection()
+ if err != nil {
+ return nil, err
+ }
+ defer c.Close()
+
+ return c.GetUserAttributes(id, attributes)
+}
+
+func (i LdapInterface) CheckPassword(id string, password string) *model.AppError {
+ c, err := i.NewLdapConnection()
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ authData, err := c.FindAuthData(id)
+ if err != nil {
+ return err
+ }
+
+ return c.CheckPassword(authData, password)
+}
+
+func (i LdapInterface) CheckPasswordAuthData(authData string, password string) *model.AppError {
+ c, err := i.NewLdapConnection()
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ return c.CheckPassword(authData, password)
+}
+
+func (i LdapInterface) SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError {
+ c, err := i.NewLdapConnection()
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ authData, err := c.FindAuthData(ldapId)
+ if err != nil {
+ return err
+ }
+
+ if err := c.CheckPassword(authData, ldapPassword); err != nil {
+ return err
+ }
+
+ if res := <-i.App.Srv.Store.User().UpdateAuthData(userId, model.USER_AUTH_SERVICE_LDAP, &authData, "", false); res.Err != nil {
+ return res.Err
+ }
+
+ return nil
+}
+
+func (i LdapInterface) ValidateFilter(filter string) *model.AppError {
+ if _, err := ldap.CompileFilter(filter); err != nil {
+ return model.NewAppError("ValidateFilter", "ent.ldap.validate_filter.app_error", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ return nil
+}
+
+func (i LdapInterface) RunTest() *model.AppError {
+ c, err := i.NewLdapConnection()
+ if err != nil {
+ return err
+ }
+ defer c.Close()
+
+ return nil
+}
+
+func (i LdapInterface) MigrateIDAttribute(toAttribute string) error {
+ // not required for login
+ // TODO
+ return nil
+}
+
+func (i LdapInterface) StartSynchronizeJob(waitForJobToFinish bool) (*model.Job, *model.AppError) {
+ // not required for login
+ // TODO
+ return nil, nil
+}
+
+func (i LdapInterface) GetAllLdapUsers() ([]*model.User, *model.AppError) {
+ // not used (maybe used by synchronization job)
+ // TODO
+ return nil, nil
+}
+
+func (i LdapInterface) CreateLdapUser(user *model.User) (*model.User, *model.AppError) {
+ found := true
+ count := 0
+ for found {
+ if found = i.App.IsUsernameTaken(user.Username); found {
+ user.Username = user.Username + strconv.Itoa(count)
+ count++
+ }
+ }
+
+ user.AuthService = model.USER_AUTH_SERVICE_LDAP
+ user.EmailVerified = true
+
+ ruser, err := i.App.CreateUser(user)
+ if err != nil {
+ return nil, model.NewAppError("CreateUser", "ent.ldap.create_fail", nil, err.Error(), http.StatusBadRequest)
+ }
+
+ return ruser, nil
+}
+
+func (i LdapInterface) UpdateLdapUserAttrs(user *model.User, ldapUser *model.User) *model.AppError {
+ userAttrsChanged := false
+
+ if ldapUser.Username != user.Username {
+ if existingUser, _ := i.App.GetUserByUsername(ldapUser.Username); existingUser == nil {
+ user.Username = ldapUser.Username
+ userAttrsChanged = true
+ }
+ }
+
+ if ldapUser.GetFullName() != user.GetFullName() {
+ user.FirstName = ldapUser.FirstName
+ user.LastName = ldapUser.LastName
+ userAttrsChanged = true
+ }
+
+ if ldapUser.Nickname != user.Nickname {
+ user.Nickname = ldapUser.Nickname
+ userAttrsChanged = true
+ }
+
+ if ldapUser.Position != user.Position {
+ user.Position = ldapUser.Position
+ userAttrsChanged = true
+ }
+
+ if ldapUser.Email != user.Email {
+ if existingUser, _ := i.App.GetUserByEmail(ldapUser.Email); existingUser == nil {
+ user.Email = ldapUser.Email
+ userAttrsChanged = true
+ }
+ }
+
+ if userAttrsChanged {
+ result := <-i.App.Srv.Store.User().Update(user, true)
+ if result.Err != nil {
+ return result.Err
+ }
+
+ user = result.Data.([2]*model.User)[0]
+ i.App.InvalidateCacheForUser(user.Id)
+ }
+
+ return nil
+}
+
+func (i LdapInterface) GetLdapUser(ldapUser *model.User) (*model.User, *model.AppError) {
+ user, err := i.App.GetUserByAuth(ldapUser.AuthData, model.USER_AUTH_SERVICE_LDAP)
+ if err != nil {
+ if err.Id == store.MISSING_AUTH_ACCOUNT_ERROR {
+ return i.CreateLdapUser(ldapUser)
+ }
+ return nil, err
+ }
+
+ if err := i.UpdateLdapUserAttrs(user, ldapUser); err != nil {
+ return nil, err
+ }
+
+ return user, nil
+}
diff --git a/utils/config.go b/utils/config.go
index b7bd15e..6441677 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -639,17 +639,15 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L
props["PasswordRequireSymbol"] = strconv.FormatBool(*c.PasswordSettings.Symbol)
props["CustomUrlSchemes"] = strings.Join(*c.DisplaySettings.CustomUrlSchemes, ",")
+ props["LdapNicknameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.NicknameAttribute != "")
+ props["LdapFirstNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.FirstNameAttribute != "")
+ props["LdapLastNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.LastNameAttribute != "")
+
if license != nil {
props["ExperimentalHideTownSquareinLHS"] = strconv.FormatBool(*c.TeamSettings.ExperimentalHideTownSquareinLHS)
props["ExperimentalTownSquareIsReadOnly"] = strconv.FormatBool(*c.TeamSettings.ExperimentalTownSquareIsReadOnly)
props["ExperimentalEnableAuthenticationTransfer"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableAuthenticationTransfer)
- if *license.Features.LDAP {
- props["LdapNicknameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.NicknameAttribute != "")
- props["LdapFirstNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.FirstNameAttribute != "")
- props["LdapLastNameAttributeSet"] = strconv.FormatBool(*c.LdapSettings.LastNameAttribute != "")
- }
-
if *license.Features.MFA {
props["EnforceMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnforceMultifactorAuthentication)
}
@@ -783,15 +781,13 @@ func GenerateLimitedClientConfig(c *model.Config, diagnosticId string, license *
props["CustomBrandText"] = *c.TeamSettings.CustomBrandText
props["CustomDescriptionText"] = *c.TeamSettings.CustomDescriptionText
- if license != nil {
- if *license.Features.LDAP {
- props["EnableLdap"] = strconv.FormatBool(*c.LdapSettings.Enable)
- props["LdapLoginFieldName"] = *c.LdapSettings.LoginFieldName
- props["LdapLoginButtonColor"] = *c.LdapSettings.LoginButtonColor
- props["LdapLoginButtonBorderColor"] = *c.LdapSettings.LoginButtonBorderColor
- props["LdapLoginButtonTextColor"] = *c.LdapSettings.LoginButtonTextColor
- }
+ props["EnableLdap"] = strconv.FormatBool(*c.LdapSettings.Enable)
+ props["LdapLoginFieldName"] = *c.LdapSettings.LoginFieldName
+ props["LdapLoginButtonColor"] = *c.LdapSettings.LoginButtonColor
+ props["LdapLoginButtonBorderColor"] = *c.LdapSettings.LoginButtonBorderColor
+ props["LdapLoginButtonTextColor"] = *c.LdapSettings.LoginButtonTextColor
+ if license != nil {
if *license.Features.MFA {
props["EnableMultifactorAuthentication"] = strconv.FormatBool(*c.ServiceSettings.EnableMultifactorAuthentication)
}
diff --git a/utils/license.go b/utils/license.go
index b84ef70..c9784df 100644
--- a/utils/license.go
+++ b/utils/license.go
@@ -124,12 +124,12 @@ func GetLicenseFileLocation(fileLocation string) string {
func GetClientLicense(l *model.License) map[string]string {
props := make(map[string]string)
- props["IsLicensed"] = strconv.FormatBool(l != nil)
+ props["IsLicensed"] = strconv.FormatBool(true)
+ props["LDAP"] = strconv.FormatBool(true)
if l != nil {
props["Id"] = l.Id
props["Users"] = strconv.Itoa(*l.Features.Users)
- props["LDAP"] = strconv.FormatBool(*l.Features.LDAP)
props["MFA"] = strconv.FormatBool(*l.Features.MFA)
props["SAML"] = strconv.FormatBool(*l.Features.SAML)
props["Cluster"] = strconv.FormatBool(*l.Features.Cluster)