Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--internal/source/gitlab/client.go137
-rw-r--r--internal/source/gitlab/client_test.go120
-rw-r--r--internal/source/gitlab/response.go21
5 files changed, 281 insertions, 0 deletions
diff --git a/go.mod b/go.mod
index 37325f2b..f6f5d046 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.12
require (
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 // indirect
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
github.com/getsentry/raven-go v0.1.2 // indirect
github.com/golang/mock v1.3.1
diff --git a/go.sum b/go.sum
index 01a25859..ad380534 100644
--- a/go.sum
+++ b/go.sum
@@ -17,6 +17,8 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs=
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8=
diff --git a/internal/source/gitlab/client.go b/internal/source/gitlab/client.go
new file mode 100644
index 00000000..bd422a4e
--- /dev/null
+++ b/internal/source/gitlab/client.go
@@ -0,0 +1,137 @@
+package gitlab
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+ "net/url"
+ "time"
+
+ jwt "github.com/dgrijalva/jwt-go"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/httptransport"
+)
+
+// Client is a HTTP client to access Pages internal API
+type Client struct {
+ secretKey []byte
+ baseURL *url.URL
+ httpClient *http.Client
+}
+
+var (
+ errUnknown = errors.New("Unknown")
+ errNoContent = errors.New("No Content")
+ errUnauthorized = errors.New("Unauthorized")
+ errNotFound = errors.New("Not Found")
+)
+
+// NewClient initializes and returns new Client
+func NewClient(baseURL string, secretKey []byte) (*Client, error) {
+ url, err := url.Parse(baseURL)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Client{
+ secretKey: secretKey,
+ baseURL: url,
+ httpClient: &http.Client{
+ Timeout: 5 * time.Second,
+ Transport: httptransport.Transport,
+ },
+ }, nil
+}
+
+// GetVirtualDomain returns VirtualDomain configuration for the given host
+func (gc *Client) GetVirtualDomain(host string) (*VirtualDomain, error) {
+ params := map[string]string{"host": host}
+
+ resp, err := gc.get("/api/v4/internal/pages", params)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var domain VirtualDomain
+ err = json.NewDecoder(resp.Body).Decode(&domain)
+ if err != nil {
+ return nil, err
+ }
+
+ return &domain, nil
+}
+
+func (gc *Client) get(path string, params map[string]string) (*http.Response, error) {
+ endpoint, err := gc.endpoint(path, params)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := gc.request("GET", endpoint)
+ if err != nil {
+ return nil, err
+ }
+
+ resp, err := gc.httpClient.Do(req)
+ if err != nil {
+ return nil, err
+ }
+
+ switch {
+ case resp.StatusCode == http.StatusOK:
+ return resp, nil
+ case resp.StatusCode == http.StatusNoContent:
+ return resp, errNoContent
+ case resp.StatusCode == http.StatusUnauthorized:
+ return resp, errUnauthorized
+ case resp.StatusCode == http.StatusNotFound:
+ return resp, errNotFound
+ default:
+ return resp, errUnknown
+ }
+}
+
+func (gc *Client) endpoint(path string, params map[string]string) (*url.URL, error) {
+ endpoint, err := gc.baseURL.Parse(path)
+ if err != nil {
+ return nil, err
+ }
+
+ values := url.Values{}
+ for key, value := range params {
+ values.Add(key, value)
+ }
+ endpoint.RawQuery = values.Encode()
+
+ return endpoint, nil
+}
+
+func (gc *Client) request(method string, endpoint *url.URL) (*http.Request, error) {
+ req, err := http.NewRequest("GET", endpoint.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ token, err := gc.token()
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Gitlab-Pages-Api-Request", token)
+
+ return req, nil
+}
+
+func (gc *Client) token() (string, error) {
+ claims := jwt.StandardClaims{
+ Issuer: "gitlab-pages",
+ ExpiresAt: time.Now().Add(1 * time.Minute).Unix(),
+ }
+
+ token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(gc.secretKey)
+ if err != nil {
+ return "", err
+ }
+
+ return token, nil
+}
diff --git a/internal/source/gitlab/client_test.go b/internal/source/gitlab/client_test.go
new file mode 100644
index 00000000..d8c86fad
--- /dev/null
+++ b/internal/source/gitlab/client_test.go
@@ -0,0 +1,120 @@
+package gitlab
+
+import (
+ "encoding/base64"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ jwt "github.com/dgrijalva/jwt-go"
+)
+
+var (
+ encodedSecret = "e41rcFh7XBA7sNABWVCe2AZvxMsy6QDtJ8S9Ql1UiN8=" // 32 bytes, base64 encoded
+)
+
+func TestNewValidBaseURL(t *testing.T) {
+ _, err := NewClient("https://gitlab.com", secretKey())
+ require.NoError(t, err)
+}
+
+func TestNewInvalidBaseURL(t *testing.T) {
+ client, err := NewClient("%", secretKey())
+ require.Error(t, err)
+ require.Nil(t, client)
+}
+
+func TestGetVirtualDomainForErrorResponses(t *testing.T) {
+ tests := map[int]string{
+ http.StatusNoContent: "No Content",
+ http.StatusUnauthorized: "Unauthorized",
+ http.StatusNotFound: "Not Found",
+ }
+
+ for statusCode, expectedError := range tests {
+ name := fmt.Sprintf("%d %s", statusCode, expectedError)
+ t.Run(name, func(t *testing.T) {
+ mux := http.NewServeMux()
+
+ mux.HandleFunc("/api/v4/internal/pages", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(statusCode)
+ })
+
+ server := httptest.NewServer(mux)
+ defer server.Close()
+
+ client, _ := NewClient(server.URL, secretKey())
+
+ actual, err := client.GetVirtualDomain("group.gitlab.io")
+
+ require.EqualError(t, err, expectedError)
+ require.Nil(t, actual)
+ })
+ }
+}
+
+func TestGetVirtualDomainAuthenticatedRequest(t *testing.T) {
+ mux := http.NewServeMux()
+
+ mux.HandleFunc("/api/v4/internal/pages", func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, "GET", r.Method)
+ assert.Equal(t, "group.gitlab.io", r.FormValue("host"))
+
+ if checkRequest(r.Header.Get("Gitlab-Pages-Api-Request")) {
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, `{"certificate":"foo","key":"bar","lookup_paths":[{"project_id":123,"access_control":false,"source":{"type":"file","path":"mygroup/myproject/public/"},"https_only":true,"prefix":"/myproject/"}]}`)
+ } else {
+ w.WriteHeader(http.StatusUnauthorized)
+ }
+ })
+
+ server := httptest.NewServer(mux)
+ defer server.Close()
+
+ client, _ := NewClient(server.URL, secretKey())
+
+ actual, err := client.GetVirtualDomain("group.gitlab.io")
+ require.NoError(t, err)
+
+ require.Equal(t, "foo", actual.Certificate)
+ require.Equal(t, "bar", actual.Key)
+
+ lookupPath := actual.LookupPaths[0]
+ require.Equal(t, 123, lookupPath.ProjectID)
+ require.Equal(t, false, lookupPath.AccessControl)
+ require.Equal(t, true, lookupPath.HTTPSOnly)
+ require.Equal(t, "/myproject/", lookupPath.Prefix)
+
+ require.Equal(t, "file", lookupPath.Source.Type)
+ require.Equal(t, "mygroup/myproject/public/", lookupPath.Source.Path)
+}
+
+func checkRequest(tokenString string) bool {
+ token, _ := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
+ }
+
+ return secretKey(), nil
+ })
+
+ claims, ok := token.Claims.(jwt.MapClaims)
+ if !ok || !token.Valid {
+ return false
+ }
+
+ if _, ok := claims["exp"]; !ok {
+ return false
+ }
+
+ return claims["iss"] == "gitlab-pages"
+}
+
+func secretKey() []byte {
+ secretKey, _ := base64.StdEncoding.DecodeString(encodedSecret)
+ return secretKey
+}
diff --git a/internal/source/gitlab/response.go b/internal/source/gitlab/response.go
new file mode 100644
index 00000000..20597362
--- /dev/null
+++ b/internal/source/gitlab/response.go
@@ -0,0 +1,21 @@
+package gitlab
+
+// LookupPath represents a lookup path for a GitLab Pages virtual domain
+type LookupPath struct {
+ ProjectID int `json:"project_id,omitempty"`
+ AccessControl bool `json:"access_control,omitempty"`
+ HTTPSOnly bool `json:"https_only,omitempty"`
+ Prefix string `json:"prefix,omitempty"`
+ Source struct {
+ Type string `json:"type,omitempty"`
+ Path string `json:"path,omitempty"`
+ }
+}
+
+// VirtualDomain represents a GitLab Pages virtual domain
+type VirtualDomain struct {
+ Certificate string `json:"certificate,omitempty"`
+ Key string `json:"key,omitempty"`
+
+ LookupPaths []LookupPath `json:"lookup_paths"`
+}