Commit 66f6e169 by Andrej Ocenas Committed by GitHub

Security: Store datasource passwords encrypted in secureJsonData (#16175)

* Store passwords in secureJsonData

* Revert unnecessary refactors

* Fix for nil jsonSecureData value

* Remove copied encryption code from migration

* Fix wrong field reference

* Remove migration and provisioning changes

* Use password getters in datasource proxy

* Refactor password handling in datasource configs

* Add provisioning warnings

* Update documentation

* Remove migration command, moved to separate PR

* Remove unused code

* Set the upgrade version

* Remove unused code

* Remove double reference
parent 844ec82e
......@@ -22,10 +22,11 @@ datasources:
access: proxy
database: site
user: grafana
password: grafana
url: http://localhost:8086
jsonData:
timeInterval: "15s"
secureJsonData:
password: grafana
- name: gdev-opentsdb
type: opentsdb
......@@ -110,14 +111,16 @@ datasources:
url: localhost:3306
database: grafana
user: grafana
password: password
secureJsonData:
password: password
- name: gdev-mysql-ds-tests
type: mysql
url: localhost:3306
database: grafana_ds_tests
user: grafana
password: password
secureJsonData:
password: password
- name: gdev-mssql
type: mssql
......
......@@ -107,7 +107,7 @@ datasources:
orgId: 1
# <string> url
url: http://localhost:8080
# <string> database password, if used
# <string> Deprecated, use secureJsonData.password
password:
# <string> database user, if used
user:
......@@ -117,7 +117,7 @@ datasources:
basicAuth:
# <string> basic auth username
basicAuthUser:
# <string> basic auth password
# <string> Deprecated, use secureJsonData.basicAuthPassword
basicAuthPassword:
# <bool> enable/disable with credentials headers
withCredentials:
......@@ -133,6 +133,10 @@ datasources:
tlsCACert: "..."
tlsClientCert: "..."
tlsClientKey: "..."
# <string> database password, if used
password:
# <string> basic auth password
basicAuthPassword:
version: 1
# <bool> allow users to edit datasources from the UI.
editable: false
......@@ -184,8 +188,8 @@ Secure json data is a map of settings that will be encrypted with [secret key](/
| tlsCACert | string | *All* |CA cert for out going requests |
| tlsClientCert | string | *All* |TLS Client cert for outgoing requests |
| tlsClientKey | string | *All* |TLS Client key for outgoing requests |
| password | string | PostgreSQL | password |
| user | string | PostgreSQL | user |
| password | string | *All* | password |
| basicAuthPassword | string | *All* | password for basic authentication |
| accessKey | string | Cloudwatch | Access key for connecting to Cloudwatch |
| secretKey | string | Cloudwatch | Secret key for connecting to Cloudwatch |
......
......@@ -123,7 +123,18 @@ If you're using systemd and have a large amount of annotations consider temporar
If you have text panels with script tags they will no longer work due to a new setting that per default disallow unsanitized HTML.
Read more [here](/installation/configuration/#disable-sanitize-html) about this new setting.
### Authentication and security
## Upgrading to v6.2
Datasources store passwords and basic auth passwords in secureJsonData encrypted by default. Existing datasource
will keep working with unencrypted passwords. If you want to migrate to encrypted storage for your existing datasources
you can do that by:
- For datasources created through UI, you need to go to datasource config, re enter the password or basic auth
password and save the datasource.
- For datasources created by provisioning, you need to update your config file and use secureJsonData.password or
secureJsonData.basicAuthPassword field. See [provisioning docs](/administration/provisioning) for example of current
configuration.
## Authentication and security
If your using Grafana's builtin, LDAP (without Auth Proxy) or OAuth authentication all users will be required to login upon the next visit after the upgrade.
......
package api
import (
"github.com/grafana/grafana/pkg/util"
"strconv"
"github.com/grafana/grafana/pkg/bus"
......@@ -8,9 +9,9 @@ import (
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"
)
// getFrontendSettingsMap returns a json object with all the settings needed for front end initialisation.
func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
orgDataSources := make([]*m.DataSource, 0)
......@@ -92,7 +93,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
if ds.Access == m.DS_ACCESS_DIRECT {
if ds.BasicAuth {
dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.BasicAuthPassword)
dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.DecryptedBasicAuthPassword())
}
if ds.WithCredentials {
dsMap["withCredentials"] = ds.WithCredentials
......@@ -100,14 +101,13 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *m.ReqContext) (map[string]interf
if ds.Type == m.DS_INFLUXDB_08 {
dsMap["username"] = ds.User
dsMap["password"] = ds.Password
dsMap["password"] = ds.DecryptedPassword()
dsMap["url"] = url + "/db/" + ds.Database
}
if ds.Type == m.DS_INFLUXDB {
dsMap["username"] = ds.User
dsMap["password"] = ds.Password
dsMap["database"] = ds.Database
dsMap["password"] = ds.DecryptedPassword()
dsMap["url"] = url
}
}
......
......@@ -146,21 +146,21 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
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)
reqQueryVals.Add("p", proxy.ds.DecryptedPassword())
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))
req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.DecryptedPassword()))
}
} 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))
req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser, proxy.ds.DecryptedBasicAuthPassword()))
}
// Lookup and use custom headers
......
......@@ -3,6 +3,7 @@ package pluginproxy
import (
"bytes"
"fmt"
"github.com/grafana/grafana/pkg/components/securejsondata"
"io/ioutil"
"net/http"
"net/url"
......@@ -274,12 +275,6 @@ func TestDSRouteRule(t *testing.T) {
Convey("Should add db to url", func() {
So(req.URL.Path, ShouldEqual, "/db/site/")
})
Convey("Should add username and password", func() {
queryVals := req.URL.Query()
So(queryVals["u"][0], ShouldEqual, "user")
So(queryVals["p"][0], ShouldEqual, "password")
})
})
Convey("When proxying a data source with no keepCookies specified", func() {
......@@ -481,6 +476,26 @@ func TestDSRouteRule(t *testing.T) {
So(req.Header.Get("X-Grafana-User"), ShouldEqual, "")
})
})
Convey("When proxying data source proxy should handle authentication", func() {
tests := []*Test{
createAuthTest(m.DS_INFLUXDB_08, AUTHTYPE_PASSWORD, AUTHCHECK_QUERY, false),
createAuthTest(m.DS_INFLUXDB_08, AUTHTYPE_PASSWORD, AUTHCHECK_QUERY, true),
createAuthTest(m.DS_INFLUXDB, AUTHTYPE_PASSWORD, AUTHCHECK_HEADER, true),
createAuthTest(m.DS_INFLUXDB, AUTHTYPE_PASSWORD, AUTHCHECK_HEADER, false),
createAuthTest(m.DS_INFLUXDB, AUTHTYPE_BASIC, AUTHCHECK_HEADER, true),
createAuthTest(m.DS_INFLUXDB, AUTHTYPE_BASIC, AUTHCHECK_HEADER, false),
// These two should be enough for any other datasource at the moment. Proxy has special handling
// only for Influx, others have the same path and only BasicAuth. Non BasicAuth datasources
// do not go through proxy but through TSDB API which is not tested here.
createAuthTest(m.DS_ES, AUTHTYPE_BASIC, AUTHCHECK_HEADER, false),
createAuthTest(m.DS_ES, AUTHTYPE_BASIC, AUTHCHECK_HEADER, true),
}
for _, test := range tests {
runDatasourceAuthTest(test)
}
})
})
}
......@@ -524,3 +539,90 @@ func newFakeHTTPClient(fakeBody []byte) httpClient {
fakeBody: fakeBody,
}
}
type Test struct {
datasource *m.DataSource
checkReq func(req *http.Request)
}
const (
AUTHTYPE_PASSWORD = "password"
AUTHTYPE_BASIC = "basic"
)
const (
AUTHCHECK_QUERY = "query"
AUTHCHECK_HEADER = "header"
)
func createAuthTest(dsType string, authType string, authCheck string, useSecureJsonData bool) *Test {
// Basic user:password
base64AthHeader := "Basic dXNlcjpwYXNzd29yZA=="
test := &Test{
datasource: &m.DataSource{
Type: dsType,
JsonData: simplejson.New(),
},
}
var message string
if authType == AUTHTYPE_PASSWORD {
message = fmt.Sprintf("%v should add username and password", dsType)
test.datasource.User = "user"
if useSecureJsonData {
test.datasource.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{
"password": "password",
})
} else {
test.datasource.Password = "password"
}
} else {
message = fmt.Sprintf("%v should add basic auth username and password", dsType)
test.datasource.BasicAuth = true
test.datasource.BasicAuthUser = "user"
if useSecureJsonData {
test.datasource.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{
"basicAuthPassword": "password",
})
} else {
test.datasource.BasicAuthPassword = "password"
}
}
if useSecureJsonData {
message += " from securejsondata"
}
if authCheck == AUTHCHECK_QUERY {
message += " to query params"
test.checkReq = func(req *http.Request) {
Convey(message, func() {
queryVals := req.URL.Query()
So(queryVals["u"][0], ShouldEqual, "user")
So(queryVals["p"][0], ShouldEqual, "password")
})
}
} else {
message += " to auth header"
test.checkReq = func(req *http.Request) {
Convey(message, func() {
So(req.Header.Get("Authorization"), ShouldEqual, base64AthHeader)
})
}
}
return test
}
func runDatasourceAuthTest(test *Test) {
plugin := &plugins.DataSourcePlugin{}
ctx := &m.ReqContext{}
proxy := NewDataSourceProxy(test.datasource, plugin, ctx, "", &setting.Cfg{})
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
proxy.getDirector()(req)
test.checkReq(req)
}
......@@ -6,8 +6,25 @@ import (
"github.com/grafana/grafana/pkg/util"
)
// SecureJsonData is used to store encrypted data (for example in data_source table). Only values are separately
// encrypted.
type SecureJsonData map[string][]byte
// DecryptedValue returns single decrypted value from SecureJsonData. Similar to normal map access second return value
// is true if the key exists and false if not.
func (s SecureJsonData) DecryptedValue(key string) (string, bool) {
if value, ok := s[key]; ok {
decryptedData, err := util.Decrypt(value, setting.SecretKey)
if err != nil {
log.Fatal(4, err.Error())
}
return string(decryptedData), true
}
return "", false
}
// Decrypt returns map of the same type but where the all the values are decrypted. Opposite of what
// GetEncryptedJsonData is doing.
func (s SecureJsonData) Decrypt() map[string]string {
decrypted := make(map[string]string)
for key, data := range s {
......@@ -21,6 +38,7 @@ func (s SecureJsonData) Decrypt() map[string]string {
return decrypted
}
// GetEncryptedJsonData returns map where all keys are encrypted.
func GetEncryptedJsonData(sjd map[string]string) SecureJsonData {
encrypted := make(SecureJsonData)
for key, data := range sjd {
......
......@@ -61,6 +61,26 @@ type DataSource struct {
Updated time.Time
}
// DecryptedBasicAuthPassword returns data source basic auth password in plain text. It uses either deprecated
// basic_auth_password field or encrypted secure_json_data[basicAuthPassword] variable.
func (ds *DataSource) DecryptedBasicAuthPassword() string {
return ds.decryptedValue("basicAuthPassword", ds.BasicAuthPassword)
}
// DecryptedPassword returns data source password in plain text. It uses either deprecated password field
// or encrypted secure_json_data[password] variable.
func (ds *DataSource) DecryptedPassword() string {
return ds.decryptedValue("password", ds.Password)
}
// decryptedValue returns decrypted value from secureJsonData
func (ds *DataSource) decryptedValue(field string, fallback string) string {
if value, ok := ds.SecureJsonData.DecryptedValue(field); ok {
return value
}
return fallback
}
var knownDatasourcePlugins = map[string]bool{
DS_ES: true,
DS_GRAPHITE: true,
......
......@@ -62,8 +62,8 @@ func (cr *configReader) parseDatasourceConfig(path string, file os.FileInfo) (*D
}
if apiVersion.ApiVersion > 0 {
var v1 *DatasourcesAsConfigV1
err = yaml.Unmarshal(yamlFile, &v1)
v1 := &DatasourcesAsConfigV1{log: cr.log}
err = yaml.Unmarshal(yamlFile, v1)
if err != nil {
return nil, err
}
......
package datasources
import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
)
import "github.com/grafana/grafana/pkg/components/simplejson"
type ConfigVersion struct {
ApiVersion int64 `json:"apiVersion" yaml:"apiVersion"`
......@@ -51,6 +52,7 @@ type DatasourcesAsConfigV0 struct {
type DatasourcesAsConfigV1 struct {
ConfigVersion
log log.Logger
Datasources []*DataSourceFromConfigV1 `json:"datasources" yaml:"datasources"`
DeleteDatasources []*DeleteDatasourceConfigV1 `json:"deleteDatasources" yaml:"deleteDatasources"`
......@@ -135,6 +137,12 @@ func (cfg *DatasourcesAsConfigV1) mapToDatasourceFromConfig(apiVersion int64) *D
Editable: ds.Editable,
Version: ds.Version,
})
if ds.Password != "" {
cfg.log.Warn("[Deprecated] the use of password field is deprecated. Please use secureJsonData.password", "datasource name", ds.Name)
}
if ds.BasicAuthPassword != "" {
cfg.log.Warn("[Deprecated] the use of basicAuthPassword field is deprecated. Please use secureJsonData.basicAuthPassword", "datasource name", ds.Name)
}
}
for _, ds := range cfg.DeleteDatasources {
......
......@@ -178,6 +178,10 @@ func UpdateDataSource(cmd *m.UpdateDataSourceCommand) error {
sess.UseBool("basic_auth")
sess.UseBool("with_credentials")
sess.UseBool("read_only")
// Make sure password are zeroed out if empty. We do this as we want to migrate passwords from
// plain text fields to SecureJsonData.
sess.MustCols("password")
sess.MustCols("basic_auth_password")
var updateSession *xorm.Session
if cmd.Version != 0 {
......
......@@ -172,12 +172,12 @@ func (c *baseClientImpl) executeRequest(method, uriPath string, body []byte) (*h
if c.ds.BasicAuth {
clientLog.Debug("Request configured to use basic authentication")
req.SetBasicAuth(c.ds.BasicAuthUser, c.ds.BasicAuthPassword)
req.SetBasicAuth(c.ds.BasicAuthUser, c.ds.DecryptedBasicAuthPassword())
}
if !c.ds.BasicAuth && c.ds.User != "" {
clientLog.Debug("Request configured to use basic authentication")
req.SetBasicAuth(c.ds.User, c.ds.Password)
req.SetBasicAuth(c.ds.User, c.ds.DecryptedPassword())
}
httpClient, err := newDatasourceHttpClient(c.ds)
......
......@@ -149,7 +149,7 @@ func (e *GraphiteExecutor) createRequest(dsInfo *models.DataSource, data url.Val
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if dsInfo.BasicAuth {
req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.BasicAuthPassword)
req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.DecryptedBasicAuthPassword())
}
return req, err
......
......@@ -125,11 +125,11 @@ func (e *InfluxDBExecutor) createRequest(dsInfo *models.DataSource, query string
req.Header.Set("User-Agent", "Grafana")
if dsInfo.BasicAuth {
req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.BasicAuthPassword)
req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.DecryptedBasicAuthPassword())
}
if !dsInfo.BasicAuth && dsInfo.User != "" {
req.SetBasicAuth(dsInfo.User, dsInfo.Password)
req.SetBasicAuth(dsInfo.User, dsInfo.DecryptedPassword())
}
glog.Debug("Influxdb request", "url", req.URL.String())
......
......@@ -44,14 +44,6 @@ func newMssqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
}
func generateConnectionString(datasource *models.DataSource) (string, error) {
password := ""
for key, value := range datasource.SecureJsonData.Decrypt() {
if key == "password" {
password = value
break
}
}
server, port := util.SplitHostPortDefault(datasource.Url, "localhost", "1433")
encrypt := datasource.JsonData.Get("encrypt").MustString("false")
......@@ -60,7 +52,7 @@ func generateConnectionString(datasource *models.DataSource) (string, error) {
port,
datasource.Database,
datasource.User,
password,
datasource.DecryptedPassword(),
)
if encrypt != "false" {
connStr += fmt.Sprintf("encrypt=%s;", encrypt)
......
......@@ -28,7 +28,7 @@ func newMysqlQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoin
}
cnnstr := fmt.Sprintf("%s:%s@%s(%s)/%s?collation=utf8mb4_unicode_ci&parseTime=true&loc=UTC&allowNativePasswords=true",
datasource.User,
datasource.Password,
datasource.DecryptedPassword(),
protocol,
datasource.Url,
datasource.Database,
......
......@@ -96,7 +96,7 @@ func (e *OpenTsdbExecutor) createRequest(dsInfo *models.DataSource, data OpenTsd
req.Header.Set("Content-Type", "application/json")
if dsInfo.BasicAuth {
req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.BasicAuthPassword)
req.SetBasicAuth(dsInfo.BasicAuthUser, dsInfo.DecryptedBasicAuthPassword())
}
return req, err
......
......@@ -41,18 +41,10 @@ func newPostgresQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndp
}
func generateConnectionString(datasource *models.DataSource) string {
password := ""
for key, value := range datasource.SecureJsonData.Decrypt() {
if key == "password" {
password = value
break
}
}
sslmode := datasource.JsonData.Get("sslmode").MustString("verify-full")
u := &url.URL{
Scheme: "postgres",
User: url.UserPassword(datasource.User, password),
User: url.UserPassword(datasource.User, datasource.DecryptedPassword()),
Host: datasource.Url, Path: datasource.Database,
RawQuery: "sslmode=" + url.QueryEscape(sslmode),
}
......
......@@ -70,7 +70,7 @@ func (e *PrometheusExecutor) getClient(dsInfo *models.DataSource) (apiv1.API, er
cfg.RoundTripper = basicAuthTransport{
Transport: e.Transport,
username: dsInfo.BasicAuthUser,
password: dsInfo.BasicAuthPassword,
password: dsInfo.DecryptedBasicAuthPassword(),
}
}
......
......@@ -63,6 +63,7 @@ export function registerAngularDirectives() {
'value',
'isConfigured',
'inputWidth',
'labelWidth',
['onReset', { watchDepth: 'reference', wrapApply: true }],
['onChange', { watchDepth: 'reference', wrapApply: true }],
]);
......
......@@ -99,8 +99,14 @@
<input class="gf-form-input max-width-21" type="text" ng-model='current.basicAuthUser' placeholder="user" required></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-10">Password</span>
<input class="gf-form-input max-width-21" type="password" ng-model='current.basicAuthPassword' placeholder="password" required></input>
<secret-form-field
isConfigured="current.basicAuthPassword || current.secureJsonFields.basicAuthPassword"
value="current.secureJsonData.basicAuthPassword || ''"
on-reset="onBasicAuthPasswordReset"
on-change="onBasicAuthPasswordChange"
inputWidth="18"
labelWidth="10"
/>
</div>
</div>
......
import { coreModule } from 'app/core/core';
import { createChangeHandler, createResetHandler, PasswordFieldEnum } from '../utils/passwordHandlers';
coreModule.directive('datasourceHttpSettings', () => {
return {
......@@ -20,6 +21,9 @@ coreModule.directive('datasourceHttpSettings', () => {
$scope.getSuggestUrls = () => {
return [$scope.suggestUrl];
};
$scope.onBasicAuthPasswordReset = createResetHandler($scope, PasswordFieldEnum.BasicAuthPassword);
$scope.onBasicAuthPasswordChange = createChangeHandler($scope, PasswordFieldEnum.BasicAuthPassword);
},
},
};
......
import { createResetHandler, PasswordFieldEnum, Ctrl } from './passwordHandlers';
describe('createResetHandler', () => {
Object.keys(PasswordFieldEnum).forEach(fieldKey => {
const field = PasswordFieldEnum[fieldKey];
it(`should reset existing ${field} field`, () => {
const event: any = {
preventDefault: () => {},
};
const ctrl: Ctrl = {
current: {
[field]: 'set',
secureJsonData: {
[field]: 'set',
},
secureJsonFields: {},
},
};
createResetHandler(ctrl, field)(event);
expect(ctrl).toEqual({
current: {
[field]: null,
secureJsonData: {
[field]: '',
},
secureJsonFields: {
[field]: false,
},
},
});
});
});
});
/**
* Set of handlers for secure password field in Angular components. They handle backward compatibility with
* passwords stored in plain text fields.
*/
import { SyntheticEvent } from 'react';
export enum PasswordFieldEnum {
Password = 'password',
BasicAuthPassword = 'basicAuthPassword',
}
/**
* Basic shape for settings controllers in at the moment mostly angular datasource plugins.
*/
export type Ctrl = {
current: {
secureJsonFields: {};
secureJsonData?: {};
};
};
export const createResetHandler = (ctrl: Ctrl, field: PasswordFieldEnum) => (
event: SyntheticEvent<HTMLInputElement>
) => {
event.preventDefault();
// Reset also normal plain text password to remove it and only save it in secureJsonData.
ctrl.current[field] = null;
ctrl.current.secureJsonFields[field] = false;
ctrl.current.secureJsonData = ctrl.current.secureJsonData || {};
ctrl.current.secureJsonData[field] = '';
};
export const createChangeHandler = (ctrl: any, field: PasswordFieldEnum) => (
event: SyntheticEvent<HTMLInputElement>
) => {
ctrl.current.secureJsonData = ctrl.current.secureJsonData || {};
ctrl.current.secureJsonData[field] = event.currentTarget.value;
};
import InfluxDatasource from './datasource';
import { InfluxQueryCtrl } from './query_ctrl';
import {
createChangeHandler,
createResetHandler,
PasswordFieldEnum,
} from '../../../features/datasources/utils/passwordHandlers';
class InfluxConfigCtrl {
static templateUrl = 'partials/config.html';
current: any;
onPasswordReset: ReturnType<typeof createResetHandler>;
onPasswordChange: ReturnType<typeof createChangeHandler>;
constructor() {
this.onPasswordReset = createResetHandler(this, PasswordFieldEnum.Password);
this.onPasswordChange = createChangeHandler(this, PasswordFieldEnum.Password);
}
}
class InfluxAnnotationsQueryCtrl {
......
......@@ -16,9 +16,14 @@
<span class="gf-form-label width-10">User</span>
<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder=""></input>
</div>
<div class="gf-form max-width-15">
<span class="gf-form-label width-10">Password</span>
<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder=""></input>
<div class="gf-form">
<secret-form-field
isConfigured="ctrl.current.password || ctrl.current.secureJsonFields.password"
value="ctrl.current.secureJsonData.password || ''"
on-reset="ctrl.onPasswordReset"
on-change="ctrl.onPasswordChange"
inputWidth="9"
/>
</div>
</div>
</div>
......
import { SyntheticEvent } from 'react';
import {
createChangeHandler,
createResetHandler,
PasswordFieldEnum,
} from '../../../features/datasources/utils/passwordHandlers';
export class MssqlConfigCtrl {
static templateUrl = 'partials/config.html';
current: any;
onPasswordReset: ReturnType<typeof createResetHandler>;
onPasswordChange: ReturnType<typeof createChangeHandler>;
/** @ngInject */
constructor($scope) {
this.current.jsonData.encrypt = this.current.jsonData.encrypt || 'false';
this.onPasswordReset = createResetHandler(this, PasswordFieldEnum.Password);
this.onPasswordChange = createChangeHandler(this, PasswordFieldEnum.Password);
}
onPasswordReset = (event: SyntheticEvent<HTMLInputElement>) => {
event.preventDefault();
this.current.secureJsonFields.password = false;
this.current.secureJsonData = this.current.secureJsonData || {};
this.current.secureJsonData.password = '';
};
onPasswordChange = (event: SyntheticEvent<HTMLInputElement>) => {
this.current.secureJsonData = this.current.secureJsonData || {};
this.current.secureJsonData.password = event.currentTarget.value;
};
}
import { MysqlDatasource } from './datasource';
import { MysqlQueryCtrl } from './query_ctrl';
import {
createChangeHandler,
createResetHandler,
PasswordFieldEnum,
} from '../../../features/datasources/utils/passwordHandlers';
class MysqlConfigCtrl {
static templateUrl = 'partials/config.html';
current: any;
onPasswordReset: ReturnType<typeof createResetHandler>;
onPasswordChange: ReturnType<typeof createChangeHandler>;
constructor() {
this.onPasswordReset = createResetHandler(this, PasswordFieldEnum.Password);
this.onPasswordChange = createChangeHandler(this, PasswordFieldEnum.Password);
}
}
const defaultQuery = `SELECT
......
......@@ -16,9 +16,14 @@
<span class="gf-form-label width-7">User</span>
<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder="user"></input>
</div>
<div class="gf-form max-width-15">
<span class="gf-form-label width-7">Password</span>
<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder="password"></input>
<div class="gf-form">
<secret-form-field
isConfigured="ctrl.current.secureJsonFields.password"
value="ctrl.current.secureJsonData.password"
on-reset="ctrl.onPasswordReset"
on-change="ctrl.onPasswordChange"
inputWidth="9"
/>
</div>
</div>
......
import _ from 'lodash';
import { SyntheticEvent } from 'react';
import {
createChangeHandler,
createResetHandler,
PasswordFieldEnum,
} from '../../../features/datasources/utils/passwordHandlers';
export class PostgresConfigCtrl {
static templateUrl = 'partials/config.html';
......@@ -7,6 +11,8 @@ export class PostgresConfigCtrl {
current: any;
datasourceSrv: any;
showTimescaleDBHelp: boolean;
onPasswordReset: ReturnType<typeof createResetHandler>;
onPasswordChange: ReturnType<typeof createChangeHandler>;
/** @ngInject */
constructor($scope, datasourceSrv) {
......@@ -15,6 +21,8 @@ export class PostgresConfigCtrl {
this.current.jsonData.postgresVersion = this.current.jsonData.postgresVersion || 903;
this.showTimescaleDBHelp = false;
this.autoDetectFeatures();
this.onPasswordReset = createResetHandler(this, PasswordFieldEnum.Password);
this.onPasswordChange = createChangeHandler(this, PasswordFieldEnum.Password);
}
autoDetectFeatures() {
......@@ -53,18 +61,6 @@ export class PostgresConfigCtrl {
this.showTimescaleDBHelp = !this.showTimescaleDBHelp;
}
onPasswordReset = (event: SyntheticEvent<HTMLInputElement>) => {
event.preventDefault();
this.current.secureJsonFields.password = false;
this.current.secureJsonData = this.current.secureJsonData || {};
this.current.secureJsonData.password = '';
};
onPasswordChange = (event: SyntheticEvent<HTMLInputElement>) => {
this.current.secureJsonData = this.current.secureJsonData || {};
this.current.secureJsonData.password = event.currentTarget.value;
};
// the value portion is derived from postgres server_version_num/100
postgresVersions = [
{ name: '9.3', value: 903 },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment