diff --git a/src/app/backend/client/manager.go b/src/app/backend/client/manager.go
index 4bc5889596dc44e4c14db5835ddf43d25371dfb7..3be2266ed4ceddfc80262f466248dbc3cf2e6532 100644
--- a/src/app/backend/client/manager.go
+++ b/src/app/backend/client/manager.go
@@ -50,6 +50,8 @@ const (
JWETokenHeader = "jweToken"
// Default http header for user-agent
DefaultUserAgent = "dashboard"
+ //Impersonation Extra header
+ ImpersonateUserExtraHeader = "Impersonate-Extra-"
)
// VERSION of this binary
@@ -311,12 +313,37 @@ func (self *clientManager) buildCmdConfig(authInfo *api.AuthInfo, cfg *rest.Conf
// Extracts authorization information from the request header
func (self *clientManager) extractAuthInfo(req *restful.Request) (*api.AuthInfo, error) {
authHeader := req.HeaderParameter("Authorization")
+ impersonationHeader := req.HeaderParameter("Impersonate-User")
jweToken := req.HeaderParameter(JWETokenHeader)
// Authorization header will be more important than our token
token := self.extractTokenFromHeader(authHeader)
if len(token) > 0 {
- return &api.AuthInfo{Token: token}, nil
+
+ authInfo := &api.AuthInfo{Token: token}
+
+ if len(impersonationHeader) > 0 {
+ //there's an impersonation header, lets make sure to add it
+ authInfo.Impersonate = impersonationHeader
+
+ //Check for impersonated groups
+ if groupsImpersonationHeader := req.Request.Header["Impersonate-Group"]; len(groupsImpersonationHeader) > 0 {
+ authInfo.ImpersonateGroups = groupsImpersonationHeader
+ }
+
+ //check for extra fields
+ for headerName, headerValues := range req.Request.Header {
+ if strings.HasPrefix(headerName, ImpersonateUserExtraHeader) {
+ extraName := headerName[len(ImpersonateUserExtraHeader):]
+ if authInfo.ImpersonateUserExtra == nil {
+ authInfo.ImpersonateUserExtra = make(map[string][]string)
+ }
+ authInfo.ImpersonateUserExtra[extraName] = headerValues
+ }
+ }
+ }
+
+ return authInfo, nil
}
if self.tokenManager != nil && len(jweToken) > 0 {
diff --git a/src/app/backend/client/manager_test.go b/src/app/backend/client/manager_test.go
index 03f00322d212b2628593858fb2c3a984079f6e19..1aa12b5da1f3a9e0cfb67744e274e956fc0f5a9b 100644
--- a/src/app/backend/client/manager_test.go
+++ b/src/app/backend/client/manager_test.go
@@ -305,3 +305,289 @@ func TestClientManager_InsecureAPIExtensionsClient(t *testing.T) {
t.Fatalf("InsecureClient(): Expected insecure client not to be nil")
}
}
+
+func TestImpersonationUserClient(t *testing.T) {
+ args.GetHolderBuilder().SetEnableSkipLogin(true)
+ cases := []struct {
+ request *restful.Request
+ expected string
+ expectedImpersonationUser string
+ }{
+ {
+ &restful.Request{
+ Request: &http.Request{
+ Header: http.Header(map[string][]string{
+ "Authorization": {"Bearer test-token"},
+ "Impersonate-User": {"impersonatedUser"},
+ }),
+ TLS: &tls.ConnectionState{},
+ },
+ },
+ "test-token",
+ "impersonatedUser",
+ },
+ }
+
+ for _, c := range cases {
+ manager := NewClientManager("", "https://localhost:8080")
+ cfg, err := manager.Config(c.request)
+ //authInfo := manager.extractAuthInfo(c.request)
+ if err != nil {
+ t.Fatalf("Config(%v): Expected config to be created but error was thrown:"+
+ " %s",
+ c.request, err.Error())
+ }
+
+ if cfg.BearerToken != c.expected {
+ t.Fatalf("Config(%v): Expected token to be %s but got %s",
+ c.request, c.expected, cfg.BearerToken)
+ }
+
+ if cfg.Impersonate.UserName != c.expectedImpersonationUser {
+ t.Fatalf("Config(%v): Expected impersonated user to be %s but got %s",
+ c.request, c.expectedImpersonationUser, cfg.Impersonate.UserName)
+ }
+
+ }
+}
+
+func TestNoImpersonationUserWithNoBearerClient(t *testing.T) {
+ args.GetHolderBuilder().SetEnableSkipLogin(true)
+ cases := []struct {
+ request *restful.Request
+ }{
+ {
+ &restful.Request{
+ Request: &http.Request{
+ Header: http.Header(map[string][]string{}),
+ TLS: &tls.ConnectionState{},
+ },
+ },
+ },
+ }
+
+ for _, c := range cases {
+ manager := NewClientManager("", "https://localhost:8080")
+ cfg, err := manager.Config(c.request)
+ //authInfo := manager.extractAuthInfo(c.request)
+ if err != nil {
+ t.Fatalf("Config(%v): Expected config to be created but error was thrown:"+
+ " %s",
+ c.request, err.Error())
+ }
+
+ if len(cfg.BearerToken) > 0 {
+ t.Fatalf("Config(%v): Expected no token but got %s",
+ c.request, cfg.BearerToken)
+ }
+
+ if len(cfg.Impersonate.UserName) > 0 {
+ t.Fatalf("Config(%v): Expected no impersonated user but got %s",
+ c.request, cfg.Impersonate.UserName)
+ }
+
+ }
+}
+
+func TestImpersonationOneGroupClient(t *testing.T) {
+ args.GetHolderBuilder().SetEnableSkipLogin(true)
+ cases := []struct {
+ request *restful.Request
+ expected string
+ expectedImpersonationUser string
+ expectedImpersonationGroups []string
+ }{
+ {
+ &restful.Request{
+ Request: &http.Request{
+ Header: http.Header(map[string][]string{
+ "Authorization": {"Bearer test-token"},
+ "Impersonate-User": {"impersonatedUser"},
+ "Impersonate-Group": {"group1"},
+ }),
+ TLS: &tls.ConnectionState{},
+ },
+ },
+ "test-token",
+ "impersonatedUser",
+ []string{"group1"},
+ },
+ }
+
+ for _, c := range cases {
+ manager := NewClientManager("", "https://localhost:8080")
+ cfg, err := manager.Config(c.request)
+ //authInfo := manager.extractAuthInfo(c.request)
+ if err != nil {
+ t.Fatalf("Config(%v): Expected config to be created but error was thrown:"+
+ " %s",
+ c.request, err.Error())
+ }
+
+ if cfg.BearerToken != c.expected {
+ t.Fatalf("Config(%v): Expected token to be %s but got %s",
+ c.request, c.expected, cfg.BearerToken)
+ }
+
+ if cfg.Impersonate.UserName != c.expectedImpersonationUser {
+ t.Fatalf("Config(%v): Expected impersonated user to be %s but got %s",
+ c.request, c.expectedImpersonationUser, cfg.Impersonate.UserName)
+ }
+
+ if len(cfg.Impersonate.Groups) != 1 {
+ t.Fatalf("Config(%v): Expected one impersonated group but got %d",
+ c.request, len(cfg.Impersonate.Groups))
+ }
+
+ if cfg.Impersonate.Groups[0] != c.expectedImpersonationGroups[0] {
+ t.Fatalf("Config(%v): Expected impersonated group to be %s but got %s",
+ c.request, cfg.Impersonate.Groups[0], c.expectedImpersonationGroups[0])
+ }
+ }
+}
+
+func TestImpersonationTwoGroupClient(t *testing.T) {
+ args.GetHolderBuilder().SetEnableSkipLogin(true)
+ cases := []struct {
+ request *restful.Request
+ expected string
+ expectedImpersonationUser string
+ expectedImpersonationGroups []string
+ }{
+ {
+ &restful.Request{
+ Request: &http.Request{
+ Header: http.Header(map[string][]string{
+ "Authorization": {"Bearer test-token"},
+ "Impersonate-User": {"impersonatedUser"},
+ "Impersonate-Group": {"group1", "groups2"},
+ }),
+ TLS: &tls.ConnectionState{},
+ },
+ },
+ "test-token",
+ "impersonatedUser",
+ []string{"group1", "groups2"},
+ },
+ }
+
+ for _, c := range cases {
+ manager := NewClientManager("", "https://localhost:8080")
+ cfg, err := manager.Config(c.request)
+ //authInfo := manager.extractAuthInfo(c.request)
+ if err != nil {
+ t.Fatalf("Config(%v): Expected config to be created but error was thrown:"+
+ " %s",
+ c.request, err.Error())
+ }
+
+ if cfg.BearerToken != c.expected {
+ t.Fatalf("Config(%v): Expected token to be %s but got %s",
+ c.request, c.expected, cfg.BearerToken)
+ }
+
+ if cfg.Impersonate.UserName != c.expectedImpersonationUser {
+ t.Fatalf("Config(%v): Expected impersonated user to be %s but got %s",
+ c.request, c.expectedImpersonationUser, cfg.Impersonate.UserName)
+ }
+
+ if len(cfg.Impersonate.Groups) != 2 {
+ t.Fatalf("Config(%v): Expected two impersonated group but got %d",
+ c.request, len(cfg.Impersonate.Groups))
+ }
+
+ if cfg.Impersonate.Groups[0] != c.expectedImpersonationGroups[0] {
+ t.Fatalf("Config(%v): Expected impersonated group to be %s but got %s",
+ c.request, cfg.Impersonate.Groups[0], c.expectedImpersonationGroups[0])
+ }
+
+ if cfg.Impersonate.Groups[1] != c.expectedImpersonationGroups[1] {
+ t.Fatalf("Config(%v): Expected impersonated group to be %s but got %s",
+ c.request, cfg.Impersonate.Groups[1], c.expectedImpersonationGroups[1])
+ }
+ }
+}
+
+func TestImpersonationExtrasClient(t *testing.T) {
+ args.GetHolderBuilder().SetEnableSkipLogin(true)
+ cases := []struct {
+ request *restful.Request
+ expected string
+ expectedImpersonationUser string
+ expectedImpersonationExtra map[string][]string
+ }{
+ {
+ &restful.Request{
+ Request: &http.Request{
+ Header: http.Header(map[string][]string{
+ "Authorization": {"Bearer test-token"},
+ "Impersonate-User": {"impersonatedUser"},
+ "Impersonate-Extra-scope": {"views", "writes"},
+ "Impersonate-Extra-service": {"iguess"},
+ }),
+ TLS: &tls.ConnectionState{},
+ },
+ },
+ "test-token",
+ "impersonatedUser",
+ map[string][]string{"scope": {"views", "writes"},
+ "service": {"iguess"}},
+ },
+ }
+
+ for _, c := range cases {
+ manager := NewClientManager("", "https://localhost:8080")
+ cfg, err := manager.Config(c.request)
+ //authInfo := manager.extractAuthInfo(c.request)
+ if err != nil {
+ t.Fatalf("Config(%v): Expected config to be created but error was thrown:"+
+ " %s",
+ c.request, err.Error())
+ }
+
+ if cfg.BearerToken != c.expected {
+ t.Fatalf("Config(%v): Expected token to be %s but got %s",
+ c.request, c.expected, cfg.BearerToken)
+ }
+
+ if cfg.Impersonate.UserName != c.expectedImpersonationUser {
+ t.Fatalf("Config(%v): Expected impersonated user to be %s but got %s",
+ c.request, c.expectedImpersonationUser, cfg.Impersonate.UserName)
+ }
+
+ if len(cfg.Impersonate.Extra) != 2 {
+ t.Fatalf("Config(%v): Expected two impersonated extra but got %d",
+ c.request, len(cfg.Impersonate.Extra))
+ }
+
+ if cfg.Impersonate.Extra["service"][0] != c.expectedImpersonationExtra["service"][0] {
+ t.Fatalf("Config(%v): Expected service extra to be %s but got %s",
+ c.request, cfg.Impersonate.Extra["service"][0], c.expectedImpersonationExtra["service"][0])
+
+ }
+
+ //check multi value scope
+
+ if len(cfg.Impersonate.Extra["scope"]) != 2 {
+ t.Fatalf("Config(%v): Expected two scope impersonated extra but got %d",
+ c.request, len(cfg.Impersonate.Extra["scope"]))
+ }
+
+ if cfg.Impersonate.Extra["scope"][0] != c.expectedImpersonationExtra["scope"][0] {
+ t.Fatalf("Config(%v): Expected scope extra to be %s but got %s",
+ c.request, c.expectedImpersonationExtra["scope"][0], cfg.Impersonate.Extra["scope"][0])
+
+ }
+
+ if cfg.Impersonate.Extra["scope"][1] != c.expectedImpersonationExtra["scope"][1] {
+ t.Fatalf("Config(%v): Expected scope extra to be %s but got %s",
+ c.request, c.expectedImpersonationExtra["scope"][1], cfg.Impersonate.Extra["scope"][1])
+
+ }
+
+ if len(cfg.Impersonate.Extra["scope"]) != 2 {
+ t.Fatalf("Config(%v): Expected two scope impersonated extra but got %d",
+ c.request, len(cfg.Impersonate.Extra["scope"]))
+ }
+ }
+}
diff --git a/src/app/backend/validation/validateloginstatus.go b/src/app/backend/validation/validateloginstatus.go
index 3919483377eeb52d3312d4d7a87974dad4c7280a..2aecded1910e6d977b0c453da4b2667a371031a9 100644
--- a/src/app/backend/validation/validateloginstatus.go
+++ b/src/app/backend/validation/validateloginstatus.go
@@ -30,21 +30,34 @@ type LoginStatus struct {
// True if dashboard is configured to use HTTPS connection. It is required for secure
// data exchange during login operation.
HTTPSMode bool `json:"httpsMode"`
+ // True if impersonation is enabled
+ ImpersonationPresent bool `json:"impersonationPresent"`
+
+ // The impersonated user
+ ImpersonatedUser string `json:"impersonatedUser"`
}
// ValidateLoginStatus returns information about user login status and if request was made over HTTPS.
func ValidateLoginStatus(request *restful.Request) *LoginStatus {
authHeader := request.HeaderParameter("Authorization")
tokenHeader := request.HeaderParameter(client.JWETokenHeader)
+ impersonationHeader := request.HeaderParameter("Impersonate-User")
httpsMode := request.Request.TLS != nil
if args.Holder.GetEnableInsecureLogin() {
httpsMode = true
}
- return &LoginStatus{
- TokenPresent: len(tokenHeader) > 0,
- HeaderPresent: len(authHeader) > 0,
- HTTPSMode: httpsMode,
+ loginStatus := &LoginStatus{
+ TokenPresent: len(tokenHeader) > 0,
+ HeaderPresent: len(authHeader) > 0,
+ ImpersonationPresent: len(impersonationHeader) > 0,
+ HTTPSMode: httpsMode,
+ }
+
+ if loginStatus.ImpersonationPresent {
+ loginStatus.ImpersonatedUser = impersonationHeader
}
+
+ return loginStatus
}
diff --git a/src/app/frontend/chrome/userpanel/template.html b/src/app/frontend/chrome/userpanel/template.html
index fbdb25c60b1b9c3a5fd719b8faa115b9e2f805c8..de0f38942d24f58cd6e708b82f82d9f6a91d789d 100644
--- a/src/app/frontend/chrome/userpanel/template.html
+++ b/src/app/frontend/chrome/userpanel/template.html
@@ -18,13 +18,15 @@ limitations under the License.
- Logged in with auth header
Logged in with token
+ {{loginStatus.impersonatedUser}}
Default service account
+