<commit_before>package aks

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/Azure/go-autorest/autorest/azure"
	"github.com/gorilla/mux"
	"github.com/rancher/aks-operator/pkg/aks"
	"github.com/rancher/norman/api/access"
	"github.com/rancher/norman/httperror"
	"github.com/rancher/norman/types"
	client "github.com/rancher/rancher/pkg/client/generated/management/v3"
	v1 "github.com/rancher/rancher/pkg/generated/norman/core/v1"
	v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3"
	"github.com/rancher/rancher/pkg/namespace"
	"github.com/rancher/rancher/pkg/ref"
	mgmtSchema "github.com/rancher/rancher/pkg/schemas/management.cattle.io/v3"
	schema "github.com/rancher/rancher/pkg/schemas/management.cattle.io/v3"
	"github.com/rancher/rancher/pkg/types/config"
	"github.com/sirupsen/logrus"
)

const (
	tenantIDAnnotation          = "cluster.management.cattle.io/azure-tenant-id"
	tenantIDTimestampAnnotation = "cluster.management.cattle.io/azure-tenant-id-created-at"
	tenantIDTimeout             = time.Hour
)

type Capabilities struct {
	AuthBaseURL      string `json:"authBaseUrl"`
	BaseURL          string `json:"baseUrl"`
	TenantID         string `json:"tenantId"`
	SubscriptionID   string `json:"subscriptionId"`
	ClientID         string `json:"clientId"`
	ClientSecret     string `json:"clientSecret"`
	ResourceLocation string `json:"region"`
}

// AKS handler lists available resources in Azure API
type handler struct {
	schemas       *types.Schemas
	secretsLister v1.SecretLister
	clusterLister v3.ClusterLister
	ac            types.AccessControl
	secretClient  v1.SecretInterface
}

func NewAKSHandler(scaledContext *config.ScaledContext) http.Handler {
	return &handler{
		schemas:       scaledContext.Schemas,
		secretsLister: scaledContext.Core.Secrets(namespace.GlobalNamespace).Controller().Lister(),
		clusterLister: scaledContext.Management.Clusters("").Controller().Lister(),
		ac:            scaledContext.AccessControl,
		secretClient:  scaledContext.Core.Secrets(namespace.GlobalNamespace),
	}
}

func (h *handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
	writer.Header().Set("Content-Type", "application/json")

	capa := &Capabilities{}

	if credID := req.URL.Query().Get("cloudCredentialId"); credID != "" {
		if errCode, err := h.getCloudCredential(req, capa, credID); err != nil {
			handleErr(writer, errCode, err)
			return
		}
	} else if req.Method == http.MethodPost {
		if errCode, err := h.getCredentialsFromBody(req, capa); err != nil {
			handleErr(writer, errCode, err)
			return
		}
	} else {
		handleErr(writer, http.StatusBadRequest, fmt.Errorf("cannot access Azure API without credentials to authenticate"))
		return
	}

	var serialized []byte
	var errCode int
	var err error

	resourceType := mux.Vars(req)["resource"]

	switch resourceType {
	case "aksVersions":
		if serialized, errCode, err = listKubernetesVersions(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting kubernetes versions: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	case "aksVirtualNetworks":
		if serialized, errCode, err = listVirtualNetworks(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting networks: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	case "aksClusters":
		if serialized, errCode, err = listClusters(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting clusters: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	case "aksVMSizes":
		if serialized, errCode, err = listVMSizes(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting VM sizes: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	case "aksLocations":
		if serialized, errCode, err = listLocations(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting locations: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	default:
		handleErr(writer, httperror.NotFound.Status, fmt.Errorf("invalid endpoint %v", resourceType))
	}
}

func (h *handler) getCloudCredential(req *http.Request, cap *Capabilities, credID string) (int, error) {
	ns, name := ref.Parse(credID)
	if ns == "" || name == "" {
		logrus.Errorf("[AKS] invalid cloud credential ID %s", credID)
		return http.StatusBadRequest, fmt.Errorf("invalid cloud credential ID %s", credID)
	}

	var accessCred client.CloudCredential //var to check access
	if err := access.ByID(h.generateAPIContext(req), &schema.Version, client.CloudCredentialType, credID, &accessCred); err != nil {
		apiError, ok := err.(*httperror.APIError)
		if !ok {
			return httperror.NotFound.Status, err
		}
		if apiError.Code.Status == httperror.NotFound.Status {
			return httperror.InvalidBodyContent.Status, fmt.Errorf("cloud credential not found")
		}
		if apiError.Code.Status != httperror.PermissionDenied.Status {
			return httperror.InvalidBodyContent.Status, err
		}
		var clusterID string
		if clusterID = req.URL.Query().Get("clusterID"); clusterID == "" {
			return httperror.InvalidBodyContent.Status, fmt.Errorf("cloud credential not found")
		}<commit_msg>Add new helper method /meta/aksCheckCredentials<commit_after>package aks

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/Azure/go-autorest/autorest/azure"
	"github.com/gorilla/mux"
	"github.com/rancher/aks-operator/pkg/aks"
	"github.com/rancher/machine/drivers/azure/azureutil"
	"github.com/rancher/norman/api/access"
	"github.com/rancher/norman/httperror"
	"github.com/rancher/norman/types"
	client "github.com/rancher/rancher/pkg/client/generated/management/v3"
	v1 "github.com/rancher/rancher/pkg/generated/norman/core/v1"
	v3 "github.com/rancher/rancher/pkg/generated/norman/management.cattle.io/v3"
	"github.com/rancher/rancher/pkg/namespace"
	"github.com/rancher/rancher/pkg/ref"
	mgmtSchema "github.com/rancher/rancher/pkg/schemas/management.cattle.io/v3"
	schema "github.com/rancher/rancher/pkg/schemas/management.cattle.io/v3"
	"github.com/rancher/rancher/pkg/types/config"
	"github.com/sirupsen/logrus"
)

const (
	tenantIDAnnotation          = "cluster.management.cattle.io/azure-tenant-id"
	tenantIDTimestampAnnotation = "cluster.management.cattle.io/azure-tenant-id-created-at"
	tenantIDTimeout             = time.Hour
)

type Capabilities struct {
	AuthBaseURL      string `json:"authBaseUrl"`
	BaseURL          string `json:"baseUrl"`
	TenantID         string `json:"tenantId"`
	SubscriptionID   string `json:"subscriptionId"`
	ClientID         string `json:"clientId"`
	ClientSecret     string `json:"clientSecret"`
	ResourceLocation string `json:"region"`
}

// AKS handler lists available resources in Azure API
type handler struct {
	schemas       *types.Schemas
	secretsLister v1.SecretLister
	clusterLister v3.ClusterLister
	ac            types.AccessControl
	secretClient  v1.SecretInterface
}

func NewAKSHandler(scaledContext *config.ScaledContext) http.Handler {
	return &handler{
		schemas:       scaledContext.Schemas,
		secretsLister: scaledContext.Core.Secrets(namespace.GlobalNamespace).Controller().Lister(),
		clusterLister: scaledContext.Management.Clusters("").Controller().Lister(),
		ac:            scaledContext.AccessControl,
		secretClient:  scaledContext.Core.Secrets(namespace.GlobalNamespace),
	}
}

func (h *handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
	writer.Header().Set("Content-Type", "application/json")

	resourceType := mux.Vars(req)["resource"]

	if resourceType == "aksCheckCredentials" {
		if req.Method != http.MethodPost {
			handleErr(writer, http.StatusMethodNotAllowed, fmt.Errorf("use POST for this endpoint"))
			return
		}
		if errCode, err := h.checkCredentials(req); err != nil {
			handleErr(writer, errCode, err)
			return
		}
		return
	}

	capa := &Capabilities{}

	if credID := req.URL.Query().Get("cloudCredentialId"); credID != "" {
		if errCode, err := h.getCloudCredential(req, capa, credID); err != nil {
			handleErr(writer, errCode, err)
			return
		}
	} else if req.Method == http.MethodPost {
		if errCode, err := h.getCredentialsFromBody(req, capa); err != nil {
			handleErr(writer, errCode, err)
			return
		}
	} else {
		handleErr(writer, http.StatusBadRequest, fmt.Errorf("cannot access Azure API without credentials to authenticate"))
		return
	}

	var serialized []byte
	var errCode int
	var err error

	switch resourceType {
	case "aksVersions":
		if serialized, errCode, err = listKubernetesVersions(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting kubernetes versions: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	case "aksVirtualNetworks":
		if serialized, errCode, err = listVirtualNetworks(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting networks: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	case "aksClusters":
		if serialized, errCode, err = listClusters(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting clusters: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	case "aksVMSizes":
		if serialized, errCode, err = listVMSizes(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting VM sizes: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	case "aksLocations":
		if serialized, errCode, err = listLocations(req.Context(), capa); err != nil {
			logrus.Errorf("[aks-handler] error getting locations: %v", err)
			handleErr(writer, errCode, err)
			return
		}
		writer.Write(serialized)
	default:
		handleErr(writer, httperror.NotFound.Status, fmt.Errorf("invalid endpoint %v", resourceType))
	}
}

func (h *handler) checkCredentials(req *http.Request) (int, error) {
	cred := &Capabilities{}
	raw, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return http.StatusBadRequest, fmt.Errorf("cannot read request body: %v", err)
	}

	if err = json.Unmarshal(raw, &cred); err != nil {
		return http.StatusBadRequest, fmt.Errorf("cannot parse request body: %v", err)
	}

	if cred.SubscriptionID == "" {
		return http.StatusBadRequest, fmt.Errorf("must provide subscriptionId")
	}
	if cred.ClientID == "" {
		return http.StatusBadRequest, fmt.Errorf("must provide clientId")
	}
	if cred.ClientSecret == "" {
		return http.StatusBadRequest, fmt.Errorf("must provide clientSecret")
	}
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	if cred.TenantID == "" {
		cred.TenantID, err = azureutil.FindTenantID(ctx, azure.PublicCloud, cred.SubscriptionID)
		if err != nil {
			return http.StatusBadRequest, fmt.Errorf("could not find tenant ID: %w", err)
		}
	}
	if cred.BaseURL == "" {
		cred.BaseURL = azure.PublicCloud.ResourceManagerEndpoint
	}
	if cred.AuthBaseURL == "" {
		cred.AuthBaseURL = azure.PublicCloud.ActiveDirectoryEndpoint
	}
	client, err := NewSubscriptionServiceClient(cred)
	if err != nil {
		return http.StatusUnauthorized, fmt.Errorf("invalid credentials")
	}
	_, err = client.Get(ctx, cred.SubscriptionID)
	if err != nil {
		return http.StatusUnauthorized, fmt.Errorf("invalid credentials")
	}

	return http.StatusOK, nil
}

func (h *handler) getCloudCredential(req *http.Request, cap *Capabilities, credID string) (int, error) {
	ns, name := ref.Parse(credID)
	if ns == "" || name == "" {
		logrus.Errorf("[AKS] invalid cloud credential ID %s", credID)
		return http.StatusBadRequest, fmt.Errorf("invalid cloud credential ID %s", credID)
	}

	var accessCred client.CloudCredential //var to check access
	if err := access.ByID(h.generateAPIContext(req), &schema.Version, client.CloudCredentialType, credID, &accessCred); err != nil {
		apiError, ok := err.(*httperror.APIError)
		if !ok {
			return httperror.NotFound.Status, err
		}
		if apiError.Code.Status == httperror.NotFound.Status {
			return httperror.InvalidBodyContent.Status, fmt.Errorf("cloud credential not found")
		}
		if apiError.Code.Status != httperror.PermissionDenied.Status {
			return httperror.InvalidBodyContent.Status, err
		}
		var clusterID string
		if clusterID = req.URL.Query().Get("clusterID"); clusterID == "" {
			return httperror.InvalidBodyContent.Status, fmt.Errorf("cloud credential not found")
		}
{'fraction_non_alphanumeric': 0.1323446893787575, 'fraction_numerical': 0.0017635270541082164, 'mean_word_length': 4.479139218269653, 'pattern_counts': {'":': 10, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4616113', 'n_tokens_mistral': 4375, 'n_tokens_neox': 4172, 'n_words': 867}