2017-08-02 01:36:54 -07:00
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package app
import (
2017-09-11 10:02:02 -05:00
"context"
2017-08-02 01:36:54 -07:00
"encoding/json"
2017-09-01 09:00:27 -04:00
"io"
"io/ioutil"
2017-08-02 01:36:54 -07:00
"net/http"
2017-09-01 09:00:27 -04:00
"os"
"path/filepath"
"strings"
2017-08-02 01:36:54 -07:00
l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
2017-09-06 23:05:10 -07:00
"github.com/mattermost/mattermost-server/einterfaces"
"github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils"
2017-08-02 01:36:54 -07:00
2017-09-11 10:02:02 -05:00
builtinplugin "github.com/mattermost/mattermost-server/app/plugin"
2017-09-06 23:05:10 -07:00
"github.com/mattermost/mattermost-server/app/plugin/jira"
"github.com/mattermost/mattermost-server/app/plugin/ldapextras"
2017-09-11 10:02:02 -05:00
"github.com/mattermost/mattermost-server/plugin"
"github.com/mattermost/mattermost-server/plugin/pluginenv"
2017-08-02 01:36:54 -07:00
)
type PluginAPI struct {
2017-09-11 10:02:02 -05:00
id string
app * App
}
func ( api * PluginAPI ) LoadPluginConfiguration ( dest interface { } ) error {
if b , err := json . Marshal ( utils . Cfg . PluginSettings . Plugins [ api . id ] ) ; err != nil {
return err
} else {
return json . Unmarshal ( b , dest )
}
}
func ( api * PluginAPI ) GetTeamByName ( name string ) ( * model . Team , * model . AppError ) {
return api . app . GetTeamByName ( name )
}
func ( api * PluginAPI ) GetUserByUsername ( name string ) ( * model . User , * model . AppError ) {
return api . app . GetUserByUsername ( name )
}
func ( api * PluginAPI ) GetChannelByName ( name , teamId string ) ( * model . Channel , * model . AppError ) {
return api . app . GetChannelByName ( name , teamId )
}
func ( api * PluginAPI ) CreatePost ( post * model . Post ) ( * model . Post , * model . AppError ) {
return api . app . CreatePostMissingChannel ( post , true )
}
type BuiltInPluginAPI struct {
2017-08-02 01:36:54 -07:00
id string
router * mux . Router
2017-09-11 10:02:02 -05:00
app * App
2017-08-02 01:36:54 -07:00
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) LoadPluginConfiguration ( dest interface { } ) error {
2017-08-02 01:36:54 -07:00
if b , err := json . Marshal ( utils . Cfg . PluginSettings . Plugins [ api . id ] ) ; err != nil {
return err
} else {
return json . Unmarshal ( b , dest )
}
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) PluginRouter ( ) * mux . Router {
2017-08-02 01:36:54 -07:00
return api . router
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) GetTeamByName ( name string ) ( * model . Team , * model . AppError ) {
return api . app . GetTeamByName ( name )
2017-08-02 01:36:54 -07:00
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) GetUserByName ( name string ) ( * model . User , * model . AppError ) {
return api . app . GetUserByUsername ( name )
2017-08-02 01:36:54 -07:00
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) GetChannelByName ( teamId , name string ) ( * model . Channel , * model . AppError ) {
return api . app . GetChannelByName ( name , teamId )
2017-08-02 01:36:54 -07:00
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) GetDirectChannel ( userId1 , userId2 string ) ( * model . Channel , * model . AppError ) {
return api . app . GetDirectChannel ( userId1 , userId2 )
2017-08-02 01:36:54 -07:00
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) CreatePost ( post * model . Post ) ( * model . Post , * model . AppError ) {
return api . app . CreatePostMissingChannel ( post , true )
2017-08-02 01:36:54 -07:00
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) GetLdapUserAttributes ( userId string , attributes [ ] string ) ( map [ string ] string , * model . AppError ) {
2017-09-01 14:28:15 -04:00
ldapInterface := einterfaces . GetLdapInterface ( )
if ldapInterface == nil {
return nil , model . NewAppError ( "GetLdapUserAttributes" , "ent.ldap.disabled.app_error" , nil , "" , http . StatusNotImplemented )
}
2017-09-11 10:02:02 -05:00
user , err := api . app . GetUser ( userId )
2017-09-01 14:28:15 -04:00
if err != nil {
return nil , err
}
return ldapInterface . GetUserAttributes ( * user . AuthData , attributes )
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) GetSessionFromRequest ( r * http . Request ) ( * model . Session , * model . AppError ) {
2017-09-01 14:28:15 -04:00
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 )
}
2017-09-11 10:02:02 -05:00
session , err := api . app . GetSession ( token )
2017-09-01 14:28:15 -04:00
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
}
2017-09-11 10:02:02 -05:00
func ( api * BuiltInPluginAPI ) I18n ( id string , r * http . Request ) string {
2017-08-02 01:36:54 -07:00
if r != nil {
f , _ := utils . GetTranslationsAndLocale ( nil , r )
return f ( id )
}
f , _ := utils . GetTranslationsBySystemLocale ( )
return f ( id )
}
2017-09-11 10:02:02 -05:00
func ( a * App ) InitBuiltInPlugins ( ) {
plugins := map [ string ] builtinplugin . Plugin {
2017-09-01 14:28:15 -04:00
"jira" : & jira . Plugin { } ,
"ldapextras" : & ldapextras . Plugin { } ,
2017-08-02 01:36:54 -07:00
}
for id , p := range plugins {
l4g . Info ( "Initializing plugin: " + id )
2017-09-11 10:02:02 -05:00
api := & BuiltInPluginAPI {
2017-08-02 01:36:54 -07:00
id : id ,
2017-09-06 17:12:54 -05:00
router : a . Srv . Router . PathPrefix ( "/plugins/" + id ) . Subrouter ( ) ,
2017-09-11 10:02:02 -05:00
app : a ,
2017-08-02 01:36:54 -07:00
}
p . Initialize ( api )
}
utils . AddConfigListener ( func ( before , after * model . Config ) {
for _ , p := range plugins {
p . OnConfigurationChange ( )
}
} )
for _ , p := range plugins {
p . OnConfigurationChange ( )
}
}
2017-09-01 09:00:27 -04:00
2017-09-06 17:12:54 -05:00
func ( a * App ) ActivatePlugins ( ) {
2017-09-11 10:02:02 -05:00
if a . PluginEnv == nil {
2017-09-01 09:00:27 -04:00
l4g . Error ( "plugin env not initialized" )
return
}
2017-09-11 10:02:02 -05:00
plugins , err := a . PluginEnv . Plugins ( )
2017-09-01 09:00:27 -04:00
if err != nil {
l4g . Error ( "failed to start up plugins: " + err . Error ( ) )
return
}
for _ , plugin := range plugins {
2017-09-11 10:02:02 -05:00
err := a . PluginEnv . ActivatePlugin ( plugin . Manifest . Id )
2017-09-01 09:00:27 -04:00
if err != nil {
l4g . Error ( err . Error ( ) )
}
l4g . Info ( "Activated %v plugin" , plugin . Manifest . Id )
}
}
2017-09-06 17:12:54 -05:00
func ( a * App ) UnpackAndActivatePlugin ( pluginFile io . Reader ) ( * model . Manifest , * model . AppError ) {
2017-09-11 10:02:02 -05:00
if a . PluginEnv == nil || ! * utils . Cfg . PluginSettings . Enable {
2017-09-01 09:00:27 -04:00
return nil , model . NewAppError ( "UnpackAndActivatePlugin" , "app.plugin.disabled.app_error" , nil , "" , http . StatusNotImplemented )
}
tmpDir , err := ioutil . TempDir ( "" , "plugintmp" )
if err != nil {
2017-09-11 10:02:02 -05:00
return nil , model . NewAppError ( "UnpackAndActivatePlugin" , "app.plugin.filesystem.app_error" , nil , err . Error ( ) , http . StatusInternalServerError )
2017-09-01 09:00:27 -04:00
}
2017-09-11 10:02:02 -05:00
defer os . RemoveAll ( tmpDir )
2017-09-01 09:00:27 -04:00
2017-09-11 10:02:02 -05:00
if err := utils . ExtractTarGz ( pluginFile , tmpDir ) ; err != nil {
2017-09-01 09:00:27 -04:00
return nil , model . NewAppError ( "UnpackAndActivatePlugin" , "app.plugin.extract.app_error" , nil , err . Error ( ) , http . StatusBadRequest )
}
2017-09-11 10:02:02 -05:00
tmpPluginDir := tmpDir
dir , err := ioutil . ReadDir ( tmpDir )
if err != nil {
return nil , model . NewAppError ( "UnpackAndActivatePlugin" , "app.plugin.filesystem.app_error" , nil , err . Error ( ) , http . StatusInternalServerError )
2017-09-01 09:00:27 -04:00
}
2017-09-11 10:02:02 -05:00
if len ( dir ) == 1 && dir [ 0 ] . IsDir ( ) {
tmpPluginDir = filepath . Join ( tmpPluginDir , dir [ 0 ] . Name ( ) )
2017-09-01 09:00:27 -04:00
}
2017-09-11 10:02:02 -05:00
manifest , _ , err := model . FindManifest ( tmpPluginDir )
2017-09-01 09:00:27 -04:00
if err != nil {
return nil , model . NewAppError ( "UnpackAndActivatePlugin" , "app.plugin.manifest.app_error" , nil , err . Error ( ) , http . StatusBadRequest )
}
2017-09-11 10:02:02 -05:00
os . Rename ( tmpPluginDir , filepath . Join ( a . PluginEnv . SearchPath ( ) , manifest . Id ) )
2017-09-01 09:00:27 -04:00
if err != nil {
return nil , model . NewAppError ( "UnpackAndActivatePlugin" , "app.plugin.mvdir.app_error" , nil , err . Error ( ) , http . StatusInternalServerError )
}
// Should add manifest validation and error handling here
2017-09-11 10:02:02 -05:00
err = a . PluginEnv . ActivatePlugin ( manifest . Id )
2017-09-01 09:00:27 -04:00
if err != nil {
return nil , model . NewAppError ( "UnpackAndActivatePlugin" , "app.plugin.activate.app_error" , nil , err . Error ( ) , http . StatusBadRequest )
}
2017-09-15 08:51:46 -04:00
if manifest . HasClient ( ) {
message := model . NewWebSocketEvent ( model . WEBSOCKET_EVENT_PLUGIN_ACTIVATED , "" , "" , "" , nil )
message . Add ( "manifest" , manifest . ClientManifest ( ) )
Publish ( message )
}
2017-09-01 09:00:27 -04:00
return manifest , nil
}
2017-09-06 17:12:54 -05:00
func ( a * App ) GetActivePluginManifests ( ) ( [ ] * model . Manifest , * model . AppError ) {
2017-09-11 10:02:02 -05:00
if a . PluginEnv == nil || ! * utils . Cfg . PluginSettings . Enable {
2017-09-01 09:00:27 -04:00
return nil , model . NewAppError ( "GetActivePluginManifests" , "app.plugin.disabled.app_error" , nil , "" , http . StatusNotImplemented )
}
2017-09-15 08:51:46 -04:00
plugins := a . PluginEnv . ActivePlugins ( )
2017-09-01 09:00:27 -04:00
manifests := make ( [ ] * model . Manifest , len ( plugins ) )
for i , plugin := range plugins {
manifests [ i ] = plugin . Manifest
}
return manifests , nil
}
2017-09-06 17:12:54 -05:00
func ( a * App ) RemovePlugin ( id string ) * model . AppError {
2017-09-11 10:02:02 -05:00
if a . PluginEnv == nil || ! * utils . Cfg . PluginSettings . Enable {
2017-09-01 09:00:27 -04:00
return model . NewAppError ( "RemovePlugin" , "app.plugin.disabled.app_error" , nil , "" , http . StatusNotImplemented )
}
2017-09-15 08:51:46 -04:00
plugins := a . PluginEnv . ActivePlugins ( )
manifest := & model . Manifest { }
for _ , p := range plugins {
if p . Manifest . Id == id {
manifest = p . Manifest
break
}
}
2017-09-11 10:02:02 -05:00
err := a . PluginEnv . DeactivatePlugin ( id )
2017-09-01 09:00:27 -04:00
if err != nil {
return model . NewAppError ( "RemovePlugin" , "app.plugin.deactivate.app_error" , nil , err . Error ( ) , http . StatusBadRequest )
}
2017-09-11 10:02:02 -05:00
err = os . RemoveAll ( filepath . Join ( a . PluginEnv . SearchPath ( ) , id ) )
2017-09-01 09:00:27 -04:00
if err != nil {
return model . NewAppError ( "RemovePlugin" , "app.plugin.remove.app_error" , nil , err . Error ( ) , http . StatusInternalServerError )
}
2017-09-15 08:51:46 -04:00
if manifest . HasClient ( ) {
message := model . NewWebSocketEvent ( model . WEBSOCKET_EVENT_PLUGIN_DEACTIVATED , "" , "" , "" , nil )
message . Add ( "manifest" , manifest . ClientManifest ( ) )
Publish ( message )
2017-09-01 09:00:27 -04:00
}
2017-09-15 08:51:46 -04:00
return nil
2017-09-01 09:00:27 -04:00
}
2017-09-11 10:02:02 -05:00
func ( a * App ) InitPlugins ( pluginPath , webappPath string ) {
a . InitBuiltInPlugins ( )
if ! utils . IsLicensed ( ) || ! * utils . License ( ) . Features . FutureFeatures || ! * utils . Cfg . PluginSettings . Enable {
return
}
l4g . Info ( "Starting up plugins" )
err := os . Mkdir ( pluginPath , 0744 )
if err != nil && ! os . IsExist ( err ) {
l4g . Error ( "failed to start up plugins: " + err . Error ( ) )
return
}
2017-09-15 08:51:46 -04:00
err = os . Mkdir ( webappPath , 0744 )
if err != nil && ! os . IsExist ( err ) {
l4g . Error ( "failed to start up plugins: " + err . Error ( ) )
return
}
2017-09-11 10:02:02 -05:00
a . PluginEnv , err = pluginenv . New (
pluginenv . SearchPath ( pluginPath ) ,
pluginenv . WebappPath ( webappPath ) ,
pluginenv . APIProvider ( func ( m * model . Manifest ) ( plugin . API , error ) {
return & PluginAPI {
id : m . Id ,
app : a ,
} , nil
} ) ,
)
if err != nil {
l4g . Error ( "failed to start up plugins: " + err . Error ( ) )
return
}
2017-09-12 14:12:29 -05:00
a . PluginConfigListenerId = utils . AddConfigListener ( func ( _ , _ * model . Config ) {
2017-09-11 10:02:02 -05:00
for _ , err := range a . PluginEnv . Hooks ( ) . OnConfigurationChange ( ) {
l4g . Error ( err . Error ( ) )
}
} )
a . Srv . Router . HandleFunc ( "/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}" , a . ServePluginRequest )
a . Srv . Router . HandleFunc ( "/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}/{anything:.*}" , a . ServePluginRequest )
a . ActivatePlugins ( )
}
func ( a * App ) ServePluginRequest ( w http . ResponseWriter , r * http . Request ) {
token := ""
authHeader := r . Header . Get ( model . HEADER_AUTH )
if strings . HasPrefix ( strings . ToUpper ( authHeader ) , model . HEADER_BEARER + ":" ) {
token = authHeader [ len ( model . HEADER_BEARER ) + 1 : ]
} else if strings . HasPrefix ( strings . ToLower ( authHeader ) , model . HEADER_TOKEN + ":" ) {
token = authHeader [ len ( model . HEADER_TOKEN ) + 1 : ]
} else if cookie , _ := r . Cookie ( model . SESSION_COOKIE_TOKEN ) ; cookie != nil && ( r . Method == "GET" || r . Header . Get ( model . HEADER_REQUESTED_WITH ) == model . HEADER_REQUESTED_WITH_XML ) {
token = cookie . Value
} else {
token = r . URL . Query ( ) . Get ( "access_token" )
}
r . Header . Del ( "Mattermost-User-Id" )
if token != "" {
if session , err := a . GetSession ( token ) ; err != nil {
r . Header . Set ( "Mattermost-User-Id" , session . UserId )
}
}
cookies := r . Cookies ( )
r . Header . Del ( "Cookie" )
for _ , c := range cookies {
if c . Name != model . SESSION_COOKIE_TOKEN {
r . AddCookie ( c )
}
}
r . Header . Del ( model . HEADER_AUTH )
r . Header . Del ( "Referer" )
newQuery := r . URL . Query ( )
newQuery . Del ( "access_token" )
r . URL . RawQuery = newQuery . Encode ( )
params := mux . Vars ( r )
a . PluginEnv . Hooks ( ) . ServeHTTP ( w , r . WithContext ( context . WithValue ( r . Context ( ) , "plugin_id" , params [ "plugin_id" ] ) ) )
}
func ( a * App ) ShutDownPlugins ( ) {
if a . PluginEnv == nil {
return
}
for _ , err := range a . PluginEnv . Shutdown ( ) {
l4g . Error ( err . Error ( ) )
}
2017-09-12 14:12:29 -05:00
utils . RemoveConfigListener ( a . PluginConfigListenerId )
a . PluginConfigListenerId = ""
2017-09-11 10:02:02 -05:00
}