diff options
-rw-r--r-- | app/plugin/api.go | 8 | ||||
-rw-r--r-- | app/plugin/ldapextras/configuration.go | 9 | ||||
-rw-r--r-- | app/plugin/ldapextras/plugin.go | 73 | ||||
-rw-r--r-- | app/plugins.go | 66 | ||||
-rw-r--r-- | einterfaces/ldap.go | 1 |
5 files changed, 156 insertions, 1 deletions
diff --git a/app/plugin/api.go b/app/plugin/api.go index 41838b818..4604bf8ce 100644 --- a/app/plugin/api.go +++ b/app/plugin/api.go @@ -32,6 +32,14 @@ type API interface { // Creates a post CreatePost(post *model.Post) (*model.Post, *model.AppError) + // Get LDAP attributes for a user + GetLdapUserAttributes(userId string, attributes []string) (map[string]string, *model.AppError) + + // Temporary for built-in plugins, copied from api4/context.go ServeHTTP function. + // If a request has a valid token for an active session, the session is returned otherwise + // it errors. + GetSessionFromRequest(r *http.Request) (*model.Session, *model.AppError) + // Returns a localized string. If a request is given, its headers will be used to pick a locale. I18n(id string, r *http.Request) string } diff --git a/app/plugin/ldapextras/configuration.go b/app/plugin/ldapextras/configuration.go new file mode 100644 index 000000000..078c29925 --- /dev/null +++ b/app/plugin/ldapextras/configuration.go @@ -0,0 +1,9 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package ldapextras + +type Configuration struct { + Enabled bool + Attributes []string +} diff --git a/app/plugin/ldapextras/plugin.go b/app/plugin/ldapextras/plugin.go new file mode 100644 index 000000000..3198125aa --- /dev/null +++ b/app/plugin/ldapextras/plugin.go @@ -0,0 +1,73 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package ldapextras + +import ( + "fmt" + "net/http" + "sync/atomic" + + l4g "github.com/alecthomas/log4go" + "github.com/gorilla/mux" + + "github.com/mattermost/platform/app/plugin" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" +) + +type Plugin struct { + plugin.Base + api plugin.API + configuration atomic.Value +} + +func (p *Plugin) Initialize(api plugin.API) { + p.api = api + p.OnConfigurationChange() + api.PluginRouter().HandleFunc("/users/{user_id:[A-Za-z0-9]+}/attributes", p.handleGetAttributes).Methods("GET") +} + +func (p *Plugin) config() *Configuration { + return p.configuration.Load().(*Configuration) +} + +func (p *Plugin) OnConfigurationChange() { + var configuration Configuration + if err := p.api.LoadPluginConfiguration(&configuration); err != nil { + l4g.Error(err.Error()) + } + p.configuration.Store(&configuration) +} + +func (p *Plugin) handleGetAttributes(w http.ResponseWriter, r *http.Request) { + config := p.config() + if !config.Enabled || len(config.Attributes) == 0 { + http.Error(w, "This plugin is not configured", http.StatusNotImplemented) + return + } + + session, err := p.api.GetSessionFromRequest(r) + + if session == nil || err != nil { + http.Error(w, "Invalid session", http.StatusUnauthorized) + return + } + + // Only requires a valid session, no other permission checks required + + params := mux.Vars(r) + id := params["user_id"] + + if len(id) != 26 { + http.Error(w, "Invalid user id", http.StatusUnauthorized) + } + + attributes, err := p.api.GetLdapUserAttributes(id, config.Attributes) + if err != nil { + err.Translate(utils.T) + http.Error(w, fmt.Sprintf("Errored getting attributes: %v", err.Error()), http.StatusInternalServerError) + } + + w.Write([]byte(model.MapToJson(attributes))) +} diff --git a/app/plugins.go b/app/plugins.go index 51f6414a3..1101f2b65 100644 --- a/app/plugins.go +++ b/app/plugins.go @@ -15,11 +15,13 @@ import ( l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" + "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" "github.com/mattermost/platform/utils" "github.com/mattermost/platform/app/plugin" "github.com/mattermost/platform/app/plugin/jira" + "github.com/mattermost/platform/app/plugin/ldapextras" ) type PluginAPI struct { @@ -59,6 +61,67 @@ func (api *PluginAPI) CreatePost(post *model.Post) (*model.Post, *model.AppError return CreatePostMissingChannel(post, true) } +func (api *PluginAPI) GetLdapUserAttributes(userId string, attributes []string) (map[string]string, *model.AppError) { + ldapInterface := einterfaces.GetLdapInterface() + if ldapInterface == nil { + return nil, model.NewAppError("GetLdapUserAttributes", "ent.ldap.disabled.app_error", nil, "", http.StatusNotImplemented) + } + + user, err := GetUser(userId) + if err != nil { + return nil, err + } + + return ldapInterface.GetUserAttributes(*user.AuthData, attributes) +} + +func (api *PluginAPI) GetSessionFromRequest(r *http.Request) (*model.Session, *model.AppError) { + token := "" + isTokenFromQueryString := false + + // Attempt to parse token out of the header + authHeader := r.Header.Get(model.HEADER_AUTH) + if len(authHeader) > 6 && strings.ToUpper(authHeader[0:6]) == model.HEADER_BEARER { + // Default session token + token = authHeader[7:] + + } else if len(authHeader) > 5 && strings.ToLower(authHeader[0:5]) == model.HEADER_TOKEN { + // OAuth token + token = authHeader[6:] + } + + // Attempt to parse the token from the cookie + if len(token) == 0 { + if cookie, err := r.Cookie(model.SESSION_COOKIE_TOKEN); err == nil { + token = cookie.Value + + if r.Header.Get(model.HEADER_REQUESTED_WITH) != model.HEADER_REQUESTED_WITH_XML { + return nil, model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token+" Appears to be a CSRF attempt", http.StatusUnauthorized) + } + } + } + + // Attempt to parse token out of the query string + if len(token) == 0 { + token = r.URL.Query().Get("access_token") + isTokenFromQueryString = true + } + + if len(token) == 0 { + return nil, model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) + } + + session, err := GetSession(token) + + if err != nil { + return nil, model.NewAppError("ServeHTTP", "api.context.session_expired.app_error", nil, "token="+token, http.StatusUnauthorized) + } else if !session.IsOAuth && isTokenFromQueryString { + return nil, model.NewAppError("ServeHTTP", "api.context.token_provided.app_error", nil, "token="+token, http.StatusUnauthorized) + } + + return session, nil +} + func (api *PluginAPI) I18n(id string, r *http.Request) string { if r != nil { f, _ := utils.GetTranslationsAndLocale(nil, r) @@ -70,7 +133,8 @@ func (api *PluginAPI) I18n(id string, r *http.Request) string { func InitPlugins() { plugins := map[string]plugin.Plugin{ - "jira": &jira.Plugin{}, + "jira": &jira.Plugin{}, + "ldapextras": &ldapextras.Plugin{}, } for id, p := range plugins { l4g.Info("Initializing plugin: " + id) diff --git a/einterfaces/ldap.go b/einterfaces/ldap.go index 721c8d30e..8ca042777 100644 --- a/einterfaces/ldap.go +++ b/einterfaces/ldap.go @@ -10,6 +10,7 @@ import ( type LdapInterface interface { DoLogin(id string, password string) (*model.User, *model.AppError) GetUser(id string) (*model.User, *model.AppError) + GetUserAttributes(id string, attributes []string) (map[string]string, *model.AppError) CheckPassword(id string, password string) *model.AppError SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError ValidateFilter(filter string) *model.AppError |