api.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package api
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "net/http"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "k8s.io/client-go/util/homedir"
  13. )
  14. // Client represents the client for the Porter API
  15. type Client struct {
  16. BaseURL string
  17. HTTPClient *http.Client
  18. Cookie *http.Cookie
  19. CookieFilePath string
  20. Token string
  21. }
  22. // HTTPError is the Porter error response returned if a request fails
  23. type HTTPError struct {
  24. Code uint `json:"code"`
  25. Errors []string `json:"errors"`
  26. }
  27. // NewClient constructs a new client based on a set of options
  28. func NewClient(baseURL string, cookieFileName string) *Client {
  29. home := homedir.HomeDir()
  30. cookieFilePath := filepath.Join(home, ".porter", cookieFileName)
  31. client := &Client{
  32. BaseURL: baseURL,
  33. CookieFilePath: cookieFilePath,
  34. HTTPClient: &http.Client{
  35. Timeout: time.Minute,
  36. },
  37. }
  38. cookie, _ := client.getCookie()
  39. if cookie != nil {
  40. client.Cookie = cookie
  41. }
  42. return client
  43. }
  44. func NewClientWithToken(baseURL, token string) *Client {
  45. client := &Client{
  46. BaseURL: baseURL,
  47. Token: token,
  48. HTTPClient: &http.Client{
  49. Timeout: time.Minute,
  50. },
  51. }
  52. return client
  53. }
  54. func (c *Client) sendRequest(req *http.Request, v interface{}, useCookie bool) (*HTTPError, error) {
  55. req.Header.Set("Content-Type", "application/json; charset=utf-8")
  56. req.Header.Set("Accept", "application/json; charset=utf-8")
  57. if c.Token != "" {
  58. req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.Token))
  59. } else if cookie, _ := c.getCookie(); useCookie && cookie != nil {
  60. c.Cookie = cookie
  61. req.AddCookie(c.Cookie)
  62. }
  63. res, err := c.HTTPClient.Do(req)
  64. if err != nil {
  65. return nil, err
  66. }
  67. defer res.Body.Close()
  68. if cookies := res.Cookies(); len(cookies) == 1 {
  69. c.saveCookie(cookies[0])
  70. }
  71. if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
  72. var errRes HTTPError
  73. if err = json.NewDecoder(res.Body).Decode(&errRes); err == nil {
  74. return &errRes, nil
  75. }
  76. return nil, fmt.Errorf("unknown error, status code: %d", res.StatusCode)
  77. }
  78. if v != nil {
  79. body, _ := ioutil.ReadAll(res.Body)
  80. fmt.Println("BODY IS", string(body))
  81. // need to create a new stream for the body
  82. res.Body = ioutil.NopCloser(bytes.NewReader(body))
  83. if err = json.NewDecoder(res.Body).Decode(v); err != nil {
  84. return nil, err
  85. }
  86. }
  87. return nil, nil
  88. }
  89. // CookieStorage for temporary fs-based cookie storage before jwt tokens
  90. type CookieStorage struct {
  91. Cookie *http.Cookie `json:"cookie"`
  92. }
  93. // saves single cookie to file
  94. func (c *Client) saveCookie(cookie *http.Cookie) error {
  95. data, err := json.Marshal(&CookieStorage{
  96. Cookie: cookie,
  97. })
  98. if err != nil {
  99. return err
  100. }
  101. return ioutil.WriteFile(c.CookieFilePath, data, 0644)
  102. }
  103. // retrieves single cookie from file
  104. func (c *Client) getCookie() (*http.Cookie, error) {
  105. data, err := ioutil.ReadFile(c.CookieFilePath)
  106. if err != nil {
  107. return nil, err
  108. }
  109. cookie := &CookieStorage{}
  110. err = json.Unmarshal(data, cookie)
  111. if err != nil {
  112. return nil, err
  113. }
  114. return cookie.Cookie, nil
  115. }
  116. type TokenProjectID struct {
  117. ProjectID uint `json:"project_id"`
  118. }
  119. func GetProjectIDFromToken(token string) (uint, bool, error) {
  120. var encoded string
  121. if tokenSplit := strings.Split(token, "."); len(tokenSplit) != 3 {
  122. return 0, false, fmt.Errorf("invalid jwt token format")
  123. } else {
  124. encoded = tokenSplit[1]
  125. }
  126. decodedBytes, err := base64.RawStdEncoding.DecodeString(encoded)
  127. if err != nil {
  128. return 0, false, fmt.Errorf("could not decode jwt token from base64: %v", err)
  129. }
  130. res := &TokenProjectID{}
  131. err = json.Unmarshal(decodedBytes, res)
  132. if err != nil {
  133. return 0, false, fmt.Errorf("could not get token project id: %v", err)
  134. }
  135. // if the project ID is 0, this is a token signed for a user, not a specific project
  136. if res.ProjectID == 0 {
  137. return 0, false, nil
  138. }
  139. return res.ProjectID, true, nil
  140. }