Skip to content

Commit

Permalink
Add API to create user settings
Browse files Browse the repository at this point in the history
  • Loading branch information
jerolimov committed Nov 12, 2020
1 parent e37679e commit d9675c1
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 0 deletions.
13 changes: 13 additions & 0 deletions pkg/server/server.go
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/openshift/console/pkg/proxy"
"github.com/openshift/console/pkg/serverutils"
"github.com/openshift/console/pkg/terminal"
"github.com/openshift/console/pkg/usersettings"
"github.com/openshift/console/pkg/version"

graphql "github.com/graph-gophers/graphql-go"
Expand Down Expand Up @@ -389,6 +390,18 @@ func (s *Server) HTTPHandler() http.Handler {
handle("/api/console/knative-channels", authHandler(s.handleKnativeChannelCRDs))
handle("/api/console/version", authHandler(s.versionHandler))

// User settings

// serviceAccountToken := s.StaticUser.Token
// serviceAccountToken := k8sAuthServiceAccountBearerToken

userSettingHandler := usersettings.UserSettingsHandler{
Client: s.K8sClient,
Endpoint: s.K8sProxyConfig.Endpoint.String(),
Token: s.StaticUser.Token,
}
handle("/api/console/user-settings", authHandlerWithUser(userSettingHandler.HandleUserSettings))

// Helm Endpoints
helmHandlers := helmhandlerspkg.New(s.K8sProxyConfig.Endpoint.String(), s.K8sClient.Transport)
handle("/api/helm/template", authHandlerWithUser(helmHandlers.HandleHelmRenderManifests))
Expand Down
271 changes: 271 additions & 0 deletions pkg/usersettings/handlers.go
@@ -0,0 +1,271 @@
package usersettings

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"

"github.com/openshift/console/pkg/auth"
"github.com/openshift/console/pkg/proxy"
"github.com/openshift/console/pkg/serverutils"
)

type UserSettingsHandler struct {
Client *http.Client
Endpoint string
Token string
}

func (h *UserSettingsHandler) HandleUserSettings(user *auth.User, w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
response, err := h.getUserSettings(user)
if err != nil {
serverutils.SendResponse(w, http.StatusBadGateway, serverutils.ApiError{Err: fmt.Sprintf("Failed to get user settings: %v", err)})
} else {
h.copy(response, w)
}
case http.MethodPost:
response, err := h.createUserSettings(user)
if err != nil {
serverutils.SendResponse(w, http.StatusBadGateway, serverutils.ApiError{Err: fmt.Sprintf("Failed to create user settings: %v", err)})
} else {
h.copy(response, w)
}
case http.MethodDelete:
response, err := h.deleteUserSettings(user)
if err != nil {
serverutils.SendResponse(w, http.StatusBadGateway, serverutils.ApiError{Err: fmt.Sprintf("Failed to delete user settings: %v", err)})
} else {
h.copy(response, w)
}
default:
w.Header().Set("Allow", "GET POST DELETE")
serverutils.SendResponse(w, http.StatusMethodNotAllowed, serverutils.ApiError{Err: "Unsupported method, supported methods are GET POST DELETE"})
}
}

func (h *UserSettingsHandler) getNamespaceName(user *auth.User) string {
return "openshift-console-user-settings"
}

func (h *UserSettingsHandler) getUsername(user *auth.User) string {
username := ""
if len(user.Username) > 0 {
username = string(regexp.MustCompile("[^a-z]").ReplaceAll([]byte(user.Username), []byte("")))
} else {
username = "kubeadmin"
}
fmt.Printf("getUsername (id=%s, username=%s) => %s\n", user.ID, user.Username, username)
return username
}

func (h *UserSettingsHandler) getConfigMapName(user *auth.User) string {
return fmt.Sprintf("user-settings-%s", h.getUsername(user))
}

func (h *UserSettingsHandler) getRoleName(user *auth.User) string {
return fmt.Sprintf("user-settings-%s-role", h.getUsername(user))
}

func (h *UserSettingsHandler) getRoleBindingName(user *auth.User) string {
return fmt.Sprintf("user-settings-%s-rolebinding", h.getUsername(user))
}

func (h *UserSettingsHandler) getUserSettings(user *auth.User) (*http.Response, error) {
namespaceName := h.getNamespaceName(user)
configMapName := h.getConfigMapName(user)
return h.getConfigMap(namespaceName, configMapName)
}

func (h *UserSettingsHandler) createUserSettings(user *auth.User) (*http.Response, error) {
namespaceName := h.getNamespaceName(user)
configMapName := h.getConfigMapName(user)
roleName := h.getRoleName(user)
roleBindingName := h.getRoleBindingName(user)

namespace := Namespace{
ApiVersion: "v1",
Kind: "Namespace",
Metadata: Metadata{
Name: namespaceName,
},
}

role := Role{
ApiVersion: "rbac.authorization.k8s.io/v1",
Kind: "Role",
Metadata: Metadata{
Namespace: namespaceName,
Name: roleName,
},
Rules: []Rule{
Rule{
ApiGroups: []string{
"", // Core group, not "v1"
},
Resources: []string{
"configmaps", // Not "ConfigMap"
},
Verbs: []string{
"get",
"list",
"patch",
"update",
"watch",
},
ResourceNames: []string{
configMapName,
},
},
},
}

roleBinding := RoleBinding{
ApiVersion: "rbac.authorization.k8s.io/v1",
Kind: "RoleBinding",
Metadata: Metadata{
Namespace: namespaceName,
Name: roleBindingName,
},
Subjects: []RoleBindingSubject{
RoleBindingSubject{
ApiGroup: "rbac.authorization.k8s.io",
Kind: "User",
Name: user.Username,
},
},
RoleRef: RoleRef{
ApiGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: roleName,
},
}

configMap := ConfigMap{
ApiVersion: "v1",
Kind: "ConfigMap",
Metadata: Metadata{
Namespace: namespaceName,
Name: configMapName,
},
}

namespaceResponse, err := h.createNamespace(&namespace)
if err != nil {
fmt.Printf("createNamespace error: %v", err)
} else {
fmt.Printf("createNamespace response: %s", h.toString(namespaceResponse))
}

roleResponse, err := h.createRole(&role)
if err != nil {
fmt.Printf("createRole error: %v", err)
} else {
fmt.Printf("createRole response: %s", h.toString(roleResponse))
}

roleBindingResponse, err := h.createRoleBinding(&roleBinding)
if err != nil {
fmt.Printf("createRoleBinding error: %v", err)
} else {
fmt.Printf("createRoleBinding response: %s", h.toString(roleBindingResponse))
}

return h.createConfigMap(&configMap)
}

func (h *UserSettingsHandler) deleteUserSettings(user *auth.User) (*http.Response, error) {
namespaceName := h.getNamespaceName(user)
configMapName := h.getConfigMapName(user)
roleName := h.getRoleName(user)
roleBindingName := h.getRoleBindingName(user)

h.deleteRoleBinding(namespaceName, roleBindingName)
h.deleteRole(namespaceName, roleName)
return h.deleteConfigMap(namespaceName, configMapName)
}

func (h *UserSettingsHandler) createNamespace(namespace *Namespace) (*http.Response, error) {
path := fmt.Sprintf("/api/v1/namespaces")
return h.sendRequest(http.MethodPost, path, namespace)
}

func (h *UserSettingsHandler) createRole(role *Role) (*http.Response, error) {
path := fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/roles", role.Metadata.Namespace)
return h.sendRequest(http.MethodPost, path, role)
}

func (h *UserSettingsHandler) createRoleBinding(roleBinding *RoleBinding) (*http.Response, error) {
path := fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/rolebindings", roleBinding.Metadata.Namespace)
return h.sendRequest(http.MethodPost, path, roleBinding)
}

func (h *UserSettingsHandler) createConfigMap(configMap *ConfigMap) (*http.Response, error) {
path := fmt.Sprintf("/api/v1/namespaces/%s/configmaps", configMap.Metadata.Namespace)
return h.sendRequest(http.MethodPost, path, configMap)
}

func (h *UserSettingsHandler) getConfigMap(namespace string, name string) (*http.Response, error) {
path := fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", namespace, name)
return h.sendRequest(http.MethodGet, path, nil)
}

func (h *UserSettingsHandler) deleteConfigMap(namespace string, name string) (*http.Response, error) {
path := fmt.Sprintf("/api/v1/namespaces/%s/configmaps/%s", namespace, name)
return h.sendRequest(http.MethodDelete, path, nil)
}

func (h *UserSettingsHandler) deleteRole(namespace string, name string) (*http.Response, error) {
path := fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/roles/%s", namespace, name)
return h.sendRequest(http.MethodDelete, path, nil)
}

func (h *UserSettingsHandler) deleteRoleBinding(namespace string, name string) (*http.Response, error) {
path := fmt.Sprintf("/apis/rbac.authorization.k8s.io/v1/namespaces/%s/rolebindings/%s", namespace, name)
return h.sendRequest(http.MethodDelete, path, nil)
}

func (h *UserSettingsHandler) sendRequest(method string, path string, content interface{}) (*http.Response, error) {
url := proxy.SingleJoiningSlash(h.Endpoint, path)

body, err := json.Marshal(content)
if err != nil {
return nil, err
}

fmt.Printf("sendRequest %s %s\n%s\n", method, path, string(body))

req, err := http.NewRequest(method, url, bytes.NewReader(body))
if err != nil {
return nil, err
}

req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", h.Token))

response, err := h.Client.Do(req)
if err != nil {
return nil, err
}

fmt.Printf("sendRequest %d %s\n", response.StatusCode, response.Status)

return response, nil
}

func (h *UserSettingsHandler) copy(receivedResponse *http.Response, w http.ResponseWriter) {
w.Header().Set("Content-Type", receivedResponse.Header.Get("Content-Type"))
w.Header().Set("Content-Length", receivedResponse.Header.Get("Content-Length"))
w.WriteHeader(receivedResponse.StatusCode)
io.Copy(w, receivedResponse.Body)
receivedResponse.Body.Close()
}

func (h *UserSettingsHandler) toString(receivedResponse *http.Response) string {
buf := new(bytes.Buffer)
buf.ReadFrom(receivedResponse.Body)
return buf.String()
}
55 changes: 55 additions & 0 deletions pkg/usersettings/types.go
@@ -0,0 +1,55 @@
package usersettings

// TODO: Convert this to official Namespace, ConfigMap types, etc.?

type Metadata struct {
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
GenerateName string `json:"generateName,omitempty"`
}

type Namespace struct {
ApiVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Metadata Metadata `json:"metadata"`
}

type ConfigMap struct {
ApiVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Metadata Metadata `json:"metadata"`
}

type Role struct {
ApiVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Metadata Metadata `json:"metadata"`
Rules []Rule `json:"rules"`
}

type Rule struct {
ApiGroups []string `json:"apiGroups"`
Resources []string `json:"resources"`
Verbs []string `json:"verbs"`
ResourceNames []string `json:"resourceNames"`
}

type RoleBinding struct {
ApiVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Metadata Metadata `json:"metadata"`
Subjects []RoleBindingSubject `json:"subjects"`
RoleRef RoleRef `json:"roleRef"`
}

type RoleBindingSubject struct {
ApiGroup string `json:"apiGroup"`
Kind string `json:"kind"`
Name string `json:"name"`
}

type RoleRef struct {
ApiGroup string `json:"apiGroup"`
Kind string `json:"kind"`
Name string `json:"name"`
}

0 comments on commit d9675c1

Please sign in to comment.