diff --git a/api/context.go b/api/context.go index 9e05c5d87f..edcdcbfeff 100644 --- a/api/context.go +++ b/api/context.go @@ -21,6 +21,15 @@ import ( var sessionCache *utils.Cache = utils.NewLru(model.SESSION_CACHE_SIZE) +var allowedMethods []string = []string{ + "POST", + "GET", + "OPTIONS", + "PUT", + "PATCH", + "DELETE", +} + type Context struct { Session model.Session RequestId string @@ -234,6 +243,31 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } +func (cw *CorsWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if len(*utils.Cfg.ServiceSettings.AllowCorsFrom) > 0 { + origin := r.Header.Get("Origin") + if *utils.Cfg.ServiceSettings.AllowCorsFrom == "*" || strings.Contains(*utils.Cfg.ServiceSettings.AllowCorsFrom, origin) { + w.Header().Set("Access-Control-Allow-Origin", origin) + + if r.Method == "OPTIONS" { + w.Header().Set( + "Access-Control-Allow-Methods", + strings.Join(allowedMethods, ", ")) + + w.Header().Set( + "Access-Control-Allow-Headers", + r.Header.Get("Access-Control-Request-Headers")) + } + } + } + + if r.Method == "OPTIONS" { + return + } + + cw.router.ServeHTTP(w, r) +} + func GetProtocol(r *http.Request) string { if r.Header.Get(model.HEADER_FORWARDED_PROTO) == "https" { return "https" diff --git a/api/server.go b/api/server.go index 070ed7a70d..b84066cbec 100644 --- a/api/server.go +++ b/api/server.go @@ -21,6 +21,10 @@ type Server struct { Router *mux.Router } +type CorsWrapper struct { + router *mux.Router +} + var Srv *Server func NewServer() { @@ -38,7 +42,7 @@ func StartServer() { l4g.Info(utils.T("api.server.start_server.starting.info")) l4g.Info(utils.T("api.server.start_server.listening.info"), utils.Cfg.ServiceSettings.ListenAddress) - var handler http.Handler = Srv.Router + var handler http.Handler = &CorsWrapper{Srv.Router} if utils.Cfg.RateLimitSettings.EnableRateLimiter { l4g.Info(utils.T("api.server.start_server.rate.info")) @@ -65,7 +69,7 @@ func StartServer() { throttled.DefaultDeniedHandler.ServeHTTP(w, r) }) - handler = th.Throttle(Srv.Router) + handler = th.Throttle(&CorsWrapper{Srv.Router}) } go func() { diff --git a/config/config.json b/config/config.json index 2795546f80..b211b16d31 100644 --- a/config/config.json +++ b/config/config.json @@ -15,6 +15,7 @@ "EnableDeveloper": false, "EnableSecurityFixAlert": true, "EnableInsecureOutgoingConnections": false, + "AllowCorsFrom": "", "SessionLengthWebInDays": 30, "SessionLengthMobileInDays": 30, "SessionLengthSSOInDays": 30, diff --git a/model/config.go b/model/config.go index aa3dd3586c..82c51224e2 100644 --- a/model/config.go +++ b/model/config.go @@ -39,6 +39,7 @@ type ServiceSettings struct { EnableDeveloper *bool EnableSecurityFixAlert *bool EnableInsecureOutgoingConnections *bool + AllowCorsFrom *string SessionLengthWebInDays *int SessionLengthMobileInDays *int SessionLengthSSOInDays *int @@ -377,6 +378,11 @@ func (o *Config) SetDefaults() { o.ServiceSettings.WebsocketSecurePort = new(int) *o.ServiceSettings.WebsocketSecurePort = 443 } + + if o.ServiceSettings.AllowCorsFrom == nil { + o.ServiceSettings.AllowCorsFrom = new(string) + *o.ServiceSettings.AllowCorsFrom = "" + } } func (o *Config) IsValid() *AppError { diff --git a/utils/config.go b/utils/config.go index 3e4ba5c5bd..63906c3458 100644 --- a/utils/config.go +++ b/utils/config.go @@ -236,5 +236,7 @@ func getClientConfig(c *model.Config) map[string]string { props["WebsocketPort"] = fmt.Sprintf("%v", *c.ServiceSettings.WebsocketPort) props["WebsocketSecurePort"] = fmt.Sprintf("%v", *c.ServiceSettings.WebsocketSecurePort) + props["AllowCorsFrom"] = *c.ServiceSettings.AllowCorsFrom + return props } diff --git a/web/react/components/admin_console/service_settings.jsx b/web/react/components/admin_console/service_settings.jsx index 047c7eb8dd..9ed81b6a3b 100644 --- a/web/react/components/admin_console/service_settings.jsx +++ b/web/react/components/admin_console/service_settings.jsx @@ -31,6 +31,10 @@ var holders = defineMessages({ id: 'admin.service.sessionDaysEx', defaultMessage: 'Ex "30"' }, + corsExample: { + id: 'admin.service.corsEx', + defaultMessage: 'http://example.com' + }, saving: { id: 'admin.service.saving', defaultMessage: 'Saving Config...' @@ -131,6 +135,8 @@ class ServiceSettings extends React.Component { config.ServiceSettings.SessionCacheInMinutes = SessionCacheInMinutes; ReactDOM.findDOMNode(this.refs.SessionCacheInMinutes).value = SessionCacheInMinutes; + config.ServiceSettings.AllowCorsFrom = ReactDOM.findDOMNode(this.refs.AllowCorsFrom).value.trim(); + Client.saveConfig( config, () => { @@ -763,6 +769,35 @@ class ServiceSettings extends React.Component { +
+