github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/ias/client.go (about)

     1  package ias
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"strings"
    11  
    12  	kebError "github.com/kyma-project/kyma-environment-broker/internal/error"
    13  )
    14  
    15  const (
    16  	PathServiceProviders  = "/service/sps"
    17  	PathCompanyGlobal     = "/service/company/global"
    18  	PathAccess            = "/service/sps/%s/rba"
    19  	PathIdentityProviders = "/service/idp"
    20  	PathDelete            = "/service/sps/delete"
    21  	PathDeleteSecret      = "/service/sps/clientSecret"
    22  )
    23  
    24  type (
    25  	ClientConfig struct {
    26  		URL    string
    27  		ID     string
    28  		Secret string
    29  	}
    30  
    31  	Client struct {
    32  		config         ClientConfig
    33  		httpClient     *http.Client
    34  		closeBodyError error
    35  	}
    36  
    37  	Request struct {
    38  		Method  string
    39  		Path    string
    40  		Body    io.Reader
    41  		Headers map[string]string
    42  		Delete  bool
    43  	}
    44  )
    45  
    46  func NewClient(cli *http.Client, cfg ClientConfig) *Client {
    47  	return &Client{
    48  		config:     cfg,
    49  		httpClient: cli,
    50  	}
    51  }
    52  
    53  func (c *Client) SetOIDCConfiguration(spID string, payload OIDCType) error {
    54  	return c.call(c.serviceProviderPath(spID), payload)
    55  }
    56  
    57  func (c *Client) SetSAMLConfiguration(spID string, payload SAMLType) error {
    58  	return c.call(c.serviceProviderPath(spID), payload)
    59  }
    60  
    61  func (c *Client) SetAssertionAttribute(spID string, payload PostAssertionAttributes) error {
    62  	return c.call(c.serviceProviderPath(spID), payload)
    63  }
    64  
    65  func (c *Client) SetSubjectNameIdentifier(spID string, payload SubjectNameIdentifier) error {
    66  	return c.call(c.serviceProviderPath(spID), payload)
    67  }
    68  
    69  func (c *Client) SetAuthenticationAndAccess(spID string, payload AuthenticationAndAccess) error {
    70  	pathAccess := fmt.Sprintf(PathAccess, spID)
    71  
    72  	return c.call(pathAccess, payload)
    73  }
    74  
    75  func (c *Client) SetDefaultAuthenticatingIDP(payload DefaultAuthIDPConfig) error {
    76  	return c.call(PathServiceProviders, payload)
    77  }
    78  
    79  func (c *Client) GetCompany() (_ *Company, err error) {
    80  	company := &Company{}
    81  	request := &Request{Method: http.MethodGet, Path: PathCompanyGlobal}
    82  
    83  	response, err := c.do(request)
    84  	defer func() {
    85  		if closeErr := c.closeResponseBody(response); closeErr != nil {
    86  			err = kebError.AsTemporaryError(closeErr, "while closing response body with company data")
    87  		}
    88  	}()
    89  	if err != nil {
    90  		return company, fmt.Errorf("while making request to ias platform about company: %w", err)
    91  	}
    92  
    93  	err = json.NewDecoder(response.Body).Decode(company)
    94  	if err != nil {
    95  		return company, fmt.Errorf("while decoding response body with company data: %w", err)
    96  	}
    97  
    98  	return company, nil
    99  }
   100  
   101  func (c *Client) CreateServiceProvider(serviceName, companyID string) (err error) {
   102  	payload := fmt.Sprintf("sp_name=%s&company_id=%s", serviceName, companyID)
   103  	request := &Request{
   104  		Method:  http.MethodPost,
   105  		Path:    PathServiceProviders,
   106  		Body:    strings.NewReader(payload),
   107  		Headers: map[string]string{"content-type": "application/x-www-form-urlencoded"},
   108  	}
   109  
   110  	response, err := c.do(request)
   111  	defer func() {
   112  		if closeErr := c.closeResponseBody(response); closeErr != nil {
   113  			err = kebError.AsTemporaryError(closeErr, "while closing response body for ServiceProvider creation")
   114  		}
   115  	}()
   116  	if err != nil {
   117  		return fmt.Errorf("while making request with ServiceProvider creation: %w", err)
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  func (c *Client) DeleteServiceProvider(spID string) (err error) {
   124  	request := &Request{
   125  		Method: http.MethodPut,
   126  		Path:   fmt.Sprintf("%s?sp_id=%s", PathDelete, spID),
   127  		Delete: true,
   128  	}
   129  	response, err := c.do(request)
   130  	defer func() {
   131  		if closeErr := c.closeResponseBody(response); closeErr != nil {
   132  			err = kebError.AsTemporaryError(closeErr, "while closing response body for ServiceProvider deletion")
   133  		}
   134  	}()
   135  	if err != nil {
   136  		return fmt.Errorf("while making request to delete ServiceProvider: %w", err)
   137  	}
   138  
   139  	return nil
   140  }
   141  
   142  func (c *Client) DeleteSecret(payload SecretsRef) (err error) {
   143  	request, err := c.jsonRequest(PathDeleteSecret, http.MethodDelete, payload)
   144  	if err != nil {
   145  		return fmt.Errorf("while creating json request for path %s: %w", PathDeleteSecret, err)
   146  	}
   147  	request.Delete = true
   148  
   149  	response, err := c.do(request)
   150  	defer func() {
   151  		if closeErr := c.closeResponseBody(response); closeErr != nil {
   152  			err = kebError.AsTemporaryError(closeErr, "while closing response body for Secret deletion")
   153  		}
   154  	}()
   155  	if err != nil {
   156  		return fmt.Errorf("while making request to delete ServiceProvider secrets: %w", err)
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  func (c *Client) GenerateServiceProviderSecret(secretCfg SecretConfiguration) (_ *ServiceProviderSecret, err error) {
   163  	secretResponse := &ServiceProviderSecret{}
   164  	request, err := c.jsonRequest(PathServiceProviders, http.MethodPut, secretCfg)
   165  	if err != nil {
   166  		return secretResponse, fmt.Errorf("while creating request for secret provider: %w", err)
   167  	}
   168  
   169  	response, err := c.do(request)
   170  	defer func() {
   171  		if closeErr := c.closeResponseBody(response); closeErr != nil {
   172  			err = kebError.AsTemporaryError(closeErr, "while closing response body for ServiceProviderSecret generating")
   173  		}
   174  	}()
   175  	if err != nil {
   176  		return secretResponse, fmt.Errorf("while making request to generate ServiceProvider secret: %w", err)
   177  	}
   178  
   179  	err = json.NewDecoder(response.Body).Decode(secretResponse)
   180  	if err != nil {
   181  		return secretResponse, fmt.Errorf("while decoding response with secret provider: %w", err)
   182  	}
   183  
   184  	return secretResponse, nil
   185  }
   186  
   187  func (c Client) AuthenticationURL(id ProviderID) string {
   188  	return fmt.Sprintf("%s%s/%s", c.config.URL, PathIdentityProviders, id)
   189  }
   190  
   191  func (c *Client) serviceProviderPath(spID string) string {
   192  	return fmt.Sprintf("%s/%s", PathServiceProviders, spID)
   193  }
   194  
   195  func (c *Client) call(path string, payload interface{}) (err error) {
   196  	request, err := c.jsonRequest(path, http.MethodPut, payload)
   197  	if err != nil {
   198  		return fmt.Errorf("while creating json request for path %s: %w", path, err)
   199  	}
   200  
   201  	response, err := c.do(request)
   202  	defer func() {
   203  		if closeErr := c.closeResponseBody(response); closeErr != nil {
   204  			err = kebError.AsTemporaryError(closeErr, "while closing response body for call method")
   205  		}
   206  	}()
   207  	if err != nil {
   208  		return fmt.Errorf("while making request for path %s: %w", path, err)
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  func (c *Client) jsonRequest(path string, method string, payload interface{}) (*Request, error) {
   215  	buffer := &bytes.Buffer{}
   216  	encoder := json.NewEncoder(buffer)
   217  	err := encoder.Encode(payload)
   218  	if err != nil {
   219  		return &Request{}, err
   220  	}
   221  
   222  	return &Request{
   223  		Method:  method,
   224  		Path:    path,
   225  		Body:    buffer,
   226  		Headers: map[string]string{"content-type": "application/json"},
   227  	}, nil
   228  }
   229  
   230  func (c *Client) do(sciReq *Request) (*http.Response, error) {
   231  	url := fmt.Sprintf("%s%s", c.config.URL, sciReq.Path)
   232  	req, err := http.NewRequest(sciReq.Method, url, sciReq.Body)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	req.Close = true
   238  	req.SetBasicAuth(c.config.ID, c.config.Secret)
   239  	for h, v := range sciReq.Headers {
   240  		req.Header.Set(h, v)
   241  	}
   242  
   243  	response, err := c.httpClient.Do(req)
   244  	if err != nil {
   245  		return &http.Response{}, kebError.AsTemporaryError(err, "while making request")
   246  	}
   247  
   248  	switch response.StatusCode {
   249  	case http.StatusOK, http.StatusCreated, http.StatusNoContent:
   250  		return response, nil
   251  	case http.StatusNotFound:
   252  		if sciReq.Delete {
   253  			return response, nil
   254  		}
   255  	case http.StatusRequestTimeout:
   256  		return response, kebError.NewTemporaryError(c.responseErrorMessage(response))
   257  	}
   258  
   259  	if response.StatusCode >= http.StatusInternalServerError {
   260  		return response, kebError.NewTemporaryError(c.responseErrorMessage(response))
   261  	}
   262  	return response, fmt.Errorf("while sending request to IAS: %s", c.responseErrorMessage(response))
   263  }
   264  
   265  func (c *Client) closeResponseBody(response *http.Response) error {
   266  	if response == nil {
   267  		return nil
   268  	}
   269  	if response.Body == nil {
   270  		return nil
   271  	}
   272  	return response.Body.Close()
   273  }
   274  
   275  func (c *Client) responseErrorMessage(response *http.Response) string {
   276  	body, err := ioutil.ReadAll(response.Body)
   277  	if err != nil {
   278  		return fmt.Sprintf("unexpected status code %d cannot read body response: %s", response.StatusCode, err)
   279  	}
   280  	return fmt.Sprintf("unexpected status code %d with body: %s", response.StatusCode, string(body))
   281  }