| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | package pluginproxy | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"net" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/http/httputil" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-08-24 17:07:37 +08:00
										 |  |  | 	"text/template" | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-12 16:25:40 +08:00
										 |  |  | 	"github.com/opentracing/opentracing-go" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/log" | 
					
						
							|  |  |  | 	m "github.com/grafana/grafana/pkg/models" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/util" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var ( | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 	logger log.Logger   = log.New("data-proxy-log") | 
					
						
							|  |  |  | 	client *http.Client = &http.Client{ | 
					
						
							|  |  |  | 		Timeout:   time.Second * 30, | 
					
						
							|  |  |  | 		Transport: &http.Transport{Proxy: http.ProxyFromEnvironment}, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-24 17:07:37 +08:00
										 |  |  | 	tokenCache = map[int64]*jwtToken{} | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | type jwtToken struct { | 
					
						
							|  |  |  | 	ExpiresOn       time.Time `json:"-"` | 
					
						
							|  |  |  | 	ExpiresOnString string    `json:"expires_on"` | 
					
						
							|  |  |  | 	AccessToken     string    `json:"access_token"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | type DataSourceProxy struct { | 
					
						
							|  |  |  | 	ds        *m.DataSource | 
					
						
							| 
									
										
										
										
											2018-03-08 00:54:50 +08:00
										 |  |  | 	ctx       *m.ReqContext | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	targetUrl *url.URL | 
					
						
							|  |  |  | 	proxyPath string | 
					
						
							|  |  |  | 	route     *plugins.AppPluginRoute | 
					
						
							| 
									
										
										
										
											2017-08-23 16:52:31 +08:00
										 |  |  | 	plugin    *plugins.DataSourcePlugin | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-08 00:54:50 +08:00
										 |  |  | func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx *m.ReqContext, proxyPath string) *DataSourceProxy { | 
					
						
							| 
									
										
										
										
											2017-08-23 16:52:31 +08:00
										 |  |  | 	targetUrl, _ := url.Parse(ds.Url) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	return &DataSourceProxy{ | 
					
						
							|  |  |  | 		ds:        ds, | 
					
						
							| 
									
										
										
										
											2017-08-23 16:52:31 +08:00
										 |  |  | 		plugin:    plugin, | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 		ctx:       ctx, | 
					
						
							|  |  |  | 		proxyPath: proxyPath, | 
					
						
							| 
									
										
										
										
											2017-08-23 16:52:31 +08:00
										 |  |  | 		targetUrl: targetUrl, | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (proxy *DataSourceProxy) HandleRequest() { | 
					
						
							|  |  |  | 	if err := proxy.validateRequest(); err != nil { | 
					
						
							|  |  |  | 		proxy.ctx.JsonApiErr(403, err.Error(), nil) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	reverseProxy := &httputil.ReverseProxy{ | 
					
						
							|  |  |  | 		Director:      proxy.getDirector(), | 
					
						
							|  |  |  | 		FlushInterval: time.Millisecond * 200, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 	reverseProxy.Transport, err = proxy.ds.GetHttpTransport() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		proxy.ctx.JsonApiErr(400, "Unable to load TLS certificate", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	proxy.logRequest() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-12 16:25:40 +08:00
										 |  |  | 	span, ctx := opentracing.StartSpanFromContext(proxy.ctx.Req.Context(), "datasource reverse proxy") | 
					
						
							|  |  |  | 	proxy.ctx.Req.Request = proxy.ctx.Req.WithContext(ctx) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer span.Finish() | 
					
						
							|  |  |  | 	span.SetTag("datasource_id", proxy.ds.Id) | 
					
						
							|  |  |  | 	span.SetTag("datasource_type", proxy.ds.Type) | 
					
						
							|  |  |  | 	span.SetTag("user_id", proxy.ctx.SignedInUser.UserId) | 
					
						
							|  |  |  | 	span.SetTag("org_id", proxy.ctx.SignedInUser.OrgId) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-09-12 17:06:37 +08:00
										 |  |  | 	opentracing.GlobalTracer().Inject( | 
					
						
							|  |  |  | 		span.Context(), | 
					
						
							|  |  |  | 		opentracing.HTTPHeaders, | 
					
						
							|  |  |  | 		opentracing.HTTPHeadersCarrier(proxy.ctx.Req.Request.Header)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	reverseProxy.ServeHTTP(proxy.ctx.Resp, proxy.ctx.Req.Request) | 
					
						
							|  |  |  | 	proxy.ctx.Resp.Header().Del("Set-Cookie") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (proxy *DataSourceProxy) getDirector() func(req *http.Request) { | 
					
						
							|  |  |  | 	return func(req *http.Request) { | 
					
						
							|  |  |  | 		req.URL.Scheme = proxy.targetUrl.Scheme | 
					
						
							|  |  |  | 		req.URL.Host = proxy.targetUrl.Host | 
					
						
							|  |  |  | 		req.Host = proxy.targetUrl.Host | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		reqQueryVals := req.URL.Query() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if proxy.ds.Type == m.DS_INFLUXDB_08 { | 
					
						
							|  |  |  | 			req.URL.Path = util.JoinUrlFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath) | 
					
						
							|  |  |  | 			reqQueryVals.Add("u", proxy.ds.User) | 
					
						
							|  |  |  | 			reqQueryVals.Add("p", proxy.ds.Password) | 
					
						
							|  |  |  | 			req.URL.RawQuery = reqQueryVals.Encode() | 
					
						
							|  |  |  | 		} else if proxy.ds.Type == m.DS_INFLUXDB { | 
					
						
							|  |  |  | 			req.URL.Path = util.JoinUrlFragments(proxy.targetUrl.Path, proxy.proxyPath) | 
					
						
							|  |  |  | 			req.URL.RawQuery = reqQueryVals.Encode() | 
					
						
							|  |  |  | 			if !proxy.ds.BasicAuth { | 
					
						
							|  |  |  | 				req.Header.Del("Authorization") | 
					
						
							|  |  |  | 				req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.Password)) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			req.URL.Path = util.JoinUrlFragments(proxy.targetUrl.Path, proxy.proxyPath) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if proxy.ds.BasicAuth { | 
					
						
							|  |  |  | 			req.Header.Del("Authorization") | 
					
						
							|  |  |  | 			req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser, proxy.ds.BasicAuthPassword)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		dsAuth := req.Header.Get("X-DS-Authorization") | 
					
						
							|  |  |  | 		if len(dsAuth) > 0 { | 
					
						
							|  |  |  | 			req.Header.Del("X-DS-Authorization") | 
					
						
							|  |  |  | 			req.Header.Del("Authorization") | 
					
						
							|  |  |  | 			req.Header.Add("Authorization", dsAuth) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-18 06:47:15 +08:00
										 |  |  | 		// clear cookie header, except for whitelisted cookies
 | 
					
						
							|  |  |  | 		var keptCookies []*http.Cookie | 
					
						
							|  |  |  | 		if proxy.ds.JsonData != nil { | 
					
						
							|  |  |  | 			if keepCookies := proxy.ds.JsonData.Get("keepCookies"); keepCookies != nil { | 
					
						
							|  |  |  | 				keepCookieNames := keepCookies.MustStringArray() | 
					
						
							|  |  |  | 				for _, c := range req.Cookies() { | 
					
						
							|  |  |  | 					for _, v := range keepCookieNames { | 
					
						
							|  |  |  | 						if c.Name == v { | 
					
						
							|  |  |  | 							keptCookies = append(keptCookies, c) | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 		req.Header.Del("Cookie") | 
					
						
							| 
									
										
										
										
											2017-10-18 06:47:15 +08:00
										 |  |  | 		for _, c := range keptCookies { | 
					
						
							|  |  |  | 			req.AddCookie(c) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// clear X-Forwarded Host/Port/Proto headers
 | 
					
						
							|  |  |  | 		req.Header.Del("X-Forwarded-Host") | 
					
						
							|  |  |  | 		req.Header.Del("X-Forwarded-Port") | 
					
						
							|  |  |  | 		req.Header.Del("X-Forwarded-Proto") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// set X-Forwarded-For header
 | 
					
						
							|  |  |  | 		if req.RemoteAddr != "" { | 
					
						
							|  |  |  | 			remoteAddr, _, err := net.SplitHostPort(req.RemoteAddr) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							|  |  |  | 				remoteAddr = req.RemoteAddr | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if req.Header.Get("X-Forwarded-For") != "" { | 
					
						
							|  |  |  | 				req.Header.Set("X-Forwarded-For", req.Header.Get("X-Forwarded-For")+", "+remoteAddr) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				req.Header.Set("X-Forwarded-For", remoteAddr) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if proxy.route != nil { | 
					
						
							|  |  |  | 			proxy.applyRoute(req) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (proxy *DataSourceProxy) validateRequest() error { | 
					
						
							|  |  |  | 	if proxy.ds.Type == m.DS_INFLUXDB { | 
					
						
							|  |  |  | 		if proxy.ctx.Query("db") != proxy.ds.Database { | 
					
						
							|  |  |  | 			return errors.New("Datasource is not configured to allow this database") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 16:52:31 +08:00
										 |  |  | 	if !checkWhiteList(proxy.ctx, proxy.targetUrl.Host) { | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 		return errors.New("Target url is not a valid target") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if proxy.ds.Type == m.DS_PROMETHEUS { | 
					
						
							| 
									
										
										
										
											2017-11-12 15:38:10 +08:00
										 |  |  | 		if proxy.ctx.Req.Request.Method == "DELETE" { | 
					
						
							|  |  |  | 			return errors.New("Deletes not allowed on proxied Prometheus datasource") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if proxy.ctx.Req.Request.Method == "PUT" { | 
					
						
							|  |  |  | 			return errors.New("Puts not allowed on proxied Prometheus datasource") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if proxy.ctx.Req.Request.Method == "POST" && !(proxy.proxyPath == "api/v1/query" || proxy.proxyPath == "api/v1/query_range") { | 
					
						
							|  |  |  | 			return errors.New("Posts not allowed on proxied Prometheus datasource except on /query and /query_range") | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if proxy.ds.Type == m.DS_ES { | 
					
						
							|  |  |  | 		if proxy.ctx.Req.Request.Method == "DELETE" { | 
					
						
							|  |  |  | 			return errors.New("Deletes not allowed on proxied Elasticsearch datasource") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if proxy.ctx.Req.Request.Method == "PUT" { | 
					
						
							|  |  |  | 			return errors.New("Puts not allowed on proxied Elasticsearch datasource") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if proxy.ctx.Req.Request.Method == "POST" && proxy.proxyPath != "_msearch" { | 
					
						
							|  |  |  | 			return errors.New("Posts not allowed on proxied Elasticsearch datasource except on /_msearch") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// found route if there are any
 | 
					
						
							| 
									
										
										
										
											2017-08-23 16:52:31 +08:00
										 |  |  | 	if len(proxy.plugin.Routes) > 0 { | 
					
						
							|  |  |  | 		for _, route := range proxy.plugin.Routes { | 
					
						
							|  |  |  | 			// method match
 | 
					
						
							|  |  |  | 			if route.Method != "" && route.Method != "*" && route.Method != proxy.ctx.Req.Method { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 16:52:31 +08:00
										 |  |  | 			if route.ReqRole.IsValid() { | 
					
						
							|  |  |  | 				if !proxy.ctx.HasUserRole(route.ReqRole) { | 
					
						
							|  |  |  | 					return errors.New("Plugin proxy route access denied") | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-08-23 16:52:31 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if strings.HasPrefix(proxy.proxyPath, route.Path) { | 
					
						
							|  |  |  | 				proxy.route = route | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (proxy *DataSourceProxy) logRequest() { | 
					
						
							|  |  |  | 	if !setting.DataProxyLogging { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var body string | 
					
						
							|  |  |  | 	if proxy.ctx.Req.Request.Body != nil { | 
					
						
							|  |  |  | 		buffer, err := ioutil.ReadAll(proxy.ctx.Req.Request.Body) | 
					
						
							|  |  |  | 		if err == nil { | 
					
						
							|  |  |  | 			proxy.ctx.Req.Request.Body = ioutil.NopCloser(bytes.NewBuffer(buffer)) | 
					
						
							|  |  |  | 			body = string(buffer) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	logger.Info("Proxying incoming request", | 
					
						
							|  |  |  | 		"userid", proxy.ctx.UserId, | 
					
						
							|  |  |  | 		"orgid", proxy.ctx.OrgId, | 
					
						
							|  |  |  | 		"username", proxy.ctx.Login, | 
					
						
							|  |  |  | 		"datasource", proxy.ds.Type, | 
					
						
							|  |  |  | 		"uri", proxy.ctx.Req.RequestURI, | 
					
						
							|  |  |  | 		"method", proxy.ctx.Req.Request.Method, | 
					
						
							|  |  |  | 		"body", body) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-08 00:54:50 +08:00
										 |  |  | func checkWhiteList(c *m.ReqContext, host string) bool { | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	if host != "" && len(setting.DataProxyWhiteList) > 0 { | 
					
						
							|  |  |  | 		if _, exists := setting.DataProxyWhiteList[host]; !exists { | 
					
						
							|  |  |  | 			c.JsonApiErr(403, "Data proxy hostname and ip are not included in whitelist", nil) | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (proxy *DataSourceProxy) applyRoute(req *http.Request) { | 
					
						
							| 
									
										
										
										
											2017-08-23 16:52:31 +08:00
										 |  |  | 	proxy.proxyPath = strings.TrimPrefix(proxy.proxyPath, proxy.route.Path) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	data := templateData{ | 
					
						
							|  |  |  | 		JsonData:       proxy.ds.JsonData.Interface().(map[string]interface{}), | 
					
						
							|  |  |  | 		SecureJsonData: proxy.ds.SecureJsonData.Decrypt(), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	routeUrl, err := url.Parse(proxy.route.Url) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		logger.Error("Error parsing plugin route url") | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	req.URL.Scheme = routeUrl.Scheme | 
					
						
							|  |  |  | 	req.URL.Host = routeUrl.Host | 
					
						
							|  |  |  | 	req.Host = routeUrl.Host | 
					
						
							|  |  |  | 	req.URL.Path = util.JoinUrlFragments(routeUrl.Path, proxy.proxyPath) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := addHeaders(&req.Header, proxy.route, data); err != nil { | 
					
						
							|  |  |  | 		logger.Error("Failed to render plugin headers", "error", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 	if proxy.route.TokenAuth != nil { | 
					
						
							|  |  |  | 		if token, err := proxy.getAccessToken(data); err != nil { | 
					
						
							|  |  |  | 			logger.Error("Failed to get access token", "error", err) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-24 17:07:37 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	logger.Info("Requesting", "url", req.URL.String()) | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | func (proxy *DataSourceProxy) getAccessToken(data templateData) (string, error) { | 
					
						
							| 
									
										
										
										
											2017-08-24 17:07:37 +08:00
										 |  |  | 	if cachedToken, found := tokenCache[proxy.ds.Id]; found { | 
					
						
							|  |  |  | 		if cachedToken.ExpiresOn.After(time.Now().Add(time.Second * 10)) { | 
					
						
							|  |  |  | 			logger.Info("Using token from cache") | 
					
						
							|  |  |  | 			return cachedToken.AccessToken, nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 	urlInterpolated, err := interpolateString(proxy.route.TokenAuth.Url, data) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	params := make(url.Values) | 
					
						
							|  |  |  | 	for key, value := range proxy.route.TokenAuth.Params { | 
					
						
							|  |  |  | 		if interpolatedParam, err := interpolateString(value, data); err != nil { | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			params.Add(key, interpolatedParam) | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 	getTokenReq, _ := http.NewRequest("POST", urlInterpolated, bytes.NewBufferString(params.Encode())) | 
					
						
							|  |  |  | 	getTokenReq.Header.Add("Content-Type", "application/x-www-form-urlencoded") | 
					
						
							|  |  |  | 	getTokenReq.Header.Add("Content-Length", strconv.Itoa(len(params.Encode()))) | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 	resp, err := client.Do(getTokenReq) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	defer resp.Body.Close() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var token jwtToken | 
					
						
							|  |  |  | 	if err := json.NewDecoder(resp.Body).Decode(&token); err != nil { | 
					
						
							|  |  |  | 		return "", err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	expiresOnEpoch, _ := strconv.ParseInt(token.ExpiresOnString, 10, 64) | 
					
						
							|  |  |  | 	token.ExpiresOn = time.Unix(expiresOnEpoch, 0) | 
					
						
							| 
									
										
										
										
											2017-08-24 17:07:37 +08:00
										 |  |  | 	tokenCache[proxy.ds.Id] = &token | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-24 17:07:37 +08:00
										 |  |  | 	logger.Info("Got new access token", "ExpiresOn", token.ExpiresOn) | 
					
						
							|  |  |  | 	return token.AccessToken, nil | 
					
						
							| 
									
										
										
										
											2017-08-23 23:18:43 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func interpolateString(text string, data templateData) (string, error) { | 
					
						
							|  |  |  | 	t, err := template.New("content").Parse(text) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", errors.New(fmt.Sprintf("Could not parse template %s.", text)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var contentBuf bytes.Buffer | 
					
						
							|  |  |  | 	err = t.Execute(&contentBuf, data) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return "", errors.New(fmt.Sprintf("Failed to execute template %s.", text)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return contentBuf.String(), nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func addHeaders(reqHeaders *http.Header, route *plugins.AppPluginRoute, data templateData) error { | 
					
						
							|  |  |  | 	for _, header := range route.Headers { | 
					
						
							|  |  |  | 		interpolated, err := interpolateString(header.Content, data) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		reqHeaders.Add(header.Name, interpolated) | 
					
						
							| 
									
										
										
										
											2017-08-22 23:14:15 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |