summaryrefslogtreecommitdiffstats
path: root/model/ldap/ldap.go
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 /model/ldap/ldap.go
parent937b6480d534b3051cadf4a892a9aa210bec579a (diff)
downloadchat-9db4666a657bf137b371b9163a60e9c818ea31f3.tar.gz
chat-9db4666a657bf137b371b9163a60e9c818ea31f3.tar.bz2
chat-9db4666a657bf137b371b9163a60e9c818ea31f3.zip
ldap: Add own ldap authenticationspline
Diffstat (limited to 'model/ldap/ldap.go')
-rw-r--r--model/ldap/ldap.go413
1 files changed, 413 insertions, 0 deletions
diff --git a/model/ldap/ldap.go b/model/ldap/ldap.go
new file mode 100644
index 000000000..5f116f0fb
--- /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
+}