From 9dc83a6bab5824b3a53d963875e1c3feb29fcd9c Mon Sep 17 00:00:00 2001 From: nshumoogum Date: Mon, 19 Mar 2018 18:21:06 +0000 Subject: [PATCH 1/8] Use go-ns identity check function to check requests are authorised --- README.md | 1 + api/api.go | 51 ++- api/dataset.go | 112 ++++++- api/dataset_test.go | 170 +++++----- api/webendpoints_test.go | 54 ++-- auth/authentication.go | 45 --- auth/authentication_test.go | 70 ---- config/config.go | 8 +- config/config_test.go | 5 +- .../ONSdigital/go-ns/identity/README.md | 45 +++ .../go-ns/identity/authentication.go | 31 ++ .../ONSdigital/go-ns/identity/identity.go | 130 ++++++++ .../ONSdigital/go-ns/rchttp/README.md | 60 ++++ .../ONSdigital/go-ns/rchttp/client.go | 161 ++++++++++ vendor/golang.org/x/net/LICENSE | 27 ++ vendor/golang.org/x/net/PATENTS | 22 ++ vendor/golang.org/x/net/context/context.go | 56 ++++ .../x/net/context/ctxhttp/ctxhttp.go | 74 +++++ .../x/net/context/ctxhttp/ctxhttp_pre17.go | 147 +++++++++ vendor/golang.org/x/net/context/go17.go | 72 +++++ vendor/golang.org/x/net/context/go19.go | 20 ++ vendor/golang.org/x/net/context/pre_go17.go | 300 ++++++++++++++++++ vendor/golang.org/x/net/context/pre_go19.go | 109 +++++++ vendor/vendor.json | 26 ++ 24 files changed, 1534 insertions(+), 262 deletions(-) delete mode 100644 auth/authentication.go delete mode 100644 auth/authentication_test.go create mode 100644 vendor/github.com/ONSdigital/go-ns/identity/README.md create mode 100644 vendor/github.com/ONSdigital/go-ns/identity/authentication.go create mode 100644 vendor/github.com/ONSdigital/go-ns/identity/identity.go create mode 100644 vendor/github.com/ONSdigital/go-ns/rchttp/README.md create mode 100644 vendor/github.com/ONSdigital/go-ns/rchttp/client.go create mode 100644 vendor/golang.org/x/net/LICENSE create mode 100644 vendor/golang.org/x/net/PATENTS create mode 100644 vendor/golang.org/x/net/context/context.go create mode 100644 vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go create mode 100644 vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go create mode 100644 vendor/golang.org/x/net/context/go17.go create mode 100644 vendor/golang.org/x/net/context/go19.go create mode 100644 vendor/golang.org/x/net/context/pre_go17.go create mode 100644 vendor/golang.org/x/net/context/pre_go19.go diff --git a/README.md b/README.md index ad86cf12..64a2d3a3 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ one of: | HEALTHCHECK_TIMEOUT | 2s | The timeout that the healthcheck allows for checked subsystems | ENABLE_PRIVATE_ENDPOINTS | false | Enable private endpoints for the API | DOWNLOAD_SERVICE_SECRET_KEY | "QB0108EZ-825D-412C-9B1D-41EF7747F462" | A key specific for the download service to access public/private links +| ZEBEDEE_URL | "http://localhost:8082" | The host name for Zebedee ### Contributing diff --git a/api/api.go b/api/api.go index d1256888..0bacb77b 100644 --- a/api/api.go +++ b/api/api.go @@ -4,15 +4,16 @@ import ( "context" "time" - "github.com/ONSdigital/dp-dataset-api/auth" "github.com/ONSdigital/dp-dataset-api/config" "github.com/ONSdigital/dp-dataset-api/dimension" "github.com/ONSdigital/dp-dataset-api/instance" "github.com/ONSdigital/dp-dataset-api/store" "github.com/ONSdigital/dp-dataset-api/url" + "github.com/ONSdigital/go-ns/identity" "github.com/ONSdigital/go-ns/log" "github.com/ONSdigital/go-ns/server" "github.com/gorilla/mux" + "github.com/justinas/alice" ) var httpServer *server.Server @@ -31,14 +32,15 @@ type DownloadsGenerator interface { type DatasetAPI struct { dataStore store.DataStore host string + zebedeeURL string internalToken string downloadServiceToken string EnablePrePublishView bool - privateAuth *auth.Authenticator router *mux.Router urlBuilder *url.Builder downloadGenerator DownloadsGenerator healthCheckTimeout time.Duration + serviceAuthToken string } // CreateDatasetAPI manages all the routes configured to API @@ -46,7 +48,8 @@ func CreateDatasetAPI(cfg config.Configuration, dataStore store.DataStore, urlBu router := mux.NewRouter() routes(cfg, router, dataStore, urlBuilder, downloadsGenerator) - httpServer = server.New(cfg.BindAddr, router) + alice := alice.New(identity.Handler(true, cfg.ZebedeeURL)).Then(router) + httpServer = server.New(cfg.BindAddr, alice) // Disable this here to allow main to manage graceful shutdown of the entire app. httpServer.HandleOSSignals = false @@ -61,17 +64,11 @@ func CreateDatasetAPI(cfg config.Configuration, dataStore store.DataStore, urlBu func routes(cfg config.Configuration, router *mux.Router, dataStore store.DataStore, urlBuilder *url.Builder, downloadGenerator DownloadsGenerator) *DatasetAPI { - var authenticator *auth.Authenticator - - if cfg.EnablePrivateEnpoints { - authenticator = &auth.Authenticator{SecretKey: cfg.SecretKey, HeaderName: "Internal-Token"} - } - api := DatasetAPI{ - privateAuth: authenticator, dataStore: dataStore, host: cfg.DatasetAPIURL, - internalToken: cfg.SecretKey, + zebedeeURL: cfg.ZebedeeURL, + serviceAuthToken: cfg.ServiceAuthToken, downloadServiceToken: cfg.DownloadServiceSecretKey, EnablePrePublishView: cfg.EnablePrivateEnpoints, router: router, @@ -97,29 +94,29 @@ func routes(cfg config.Configuration, router *mux.Router, dataStore store.DataSt log.Debug("private endpoints have been enabled", log.Data{}) - api.router.HandleFunc("/datasets/{id}", api.privateAuth.Check(api.addDataset)).Methods("POST") - api.router.HandleFunc("/datasets/{id}", api.privateAuth.Check(api.putDataset)).Methods("PUT") - api.router.HandleFunc("/datasets/{id}", api.privateAuth.Check(api.deleteDataset)).Methods("DELETE") - api.router.HandleFunc("/datasets/{id}/editions/{edition}/versions/{version}", api.privateAuth.Check(versionPublishChecker.Check(api.putVersion))).Methods("PUT") + api.router.HandleFunc("/datasets/{id}", identity.Check(api.addDataset)).Methods("POST") + api.router.HandleFunc("/datasets/{id}", identity.Check(api.putDataset)).Methods("PUT") + api.router.HandleFunc("/datasets/{id}", identity.Check(api.deleteDataset)).Methods("DELETE") + api.router.HandleFunc("/datasets/{id}/editions/{edition}/versions/{version}", identity.Check(versionPublishChecker.Check(api.putVersion))).Methods("PUT") instanceAPI := instance.Store{Host: api.host, Storer: api.dataStore.Backend} instancePublishChecker := instance.PublishCheck{Datastore: dataStore.Backend} - api.router.HandleFunc("/instances", api.privateAuth.Check(instanceAPI.GetList)).Methods("GET") - api.router.HandleFunc("/instances", api.privateAuth.Check(instanceAPI.Add)).Methods("POST") - api.router.HandleFunc("/instances/{id}", api.privateAuth.Check(instanceAPI.Get)).Methods("GET") - api.router.HandleFunc("/instances/{id}", api.privateAuth.Check(instancePublishChecker.Check(instanceAPI.Update))).Methods("PUT") - api.router.HandleFunc("/instances/{id}/dimensions/{dimension}", api.privateAuth.Check(instancePublishChecker.Check(instanceAPI.UpdateDimension))).Methods("PUT") - api.router.HandleFunc("/instances/{id}/events", api.privateAuth.Check(instanceAPI.AddEvent)).Methods("POST") + api.router.HandleFunc("/instances", identity.Check(instanceAPI.GetList)).Methods("GET") + api.router.HandleFunc("/instances", identity.Check(instanceAPI.Add)).Methods("POST") + api.router.HandleFunc("/instances/{id}", identity.Check(instanceAPI.Get)).Methods("GET") + api.router.HandleFunc("/instances/{id}", identity.Check(instancePublishChecker.Check(instanceAPI.Update))).Methods("PUT") + api.router.HandleFunc("/instances/{id}/dimensions/{dimension}", identity.Check(instancePublishChecker.Check(instanceAPI.UpdateDimension))).Methods("PUT") + api.router.HandleFunc("/instances/{id}/events", identity.Check(instanceAPI.AddEvent)).Methods("POST") api.router.HandleFunc("/instances/{id}/inserted_observations/{inserted_observations}", - api.privateAuth.Check(instancePublishChecker.Check(instanceAPI.UpdateObservations))).Methods("PUT") - api.router.HandleFunc("/instances/{id}/import_tasks", api.privateAuth.Check(instancePublishChecker.Check(instanceAPI.UpdateImportTask))).Methods("PUT") + identity.Check(instancePublishChecker.Check(instanceAPI.UpdateObservations))).Methods("PUT") + api.router.HandleFunc("/instances/{id}/import_tasks", identity.Check(instancePublishChecker.Check(instanceAPI.UpdateImportTask))).Methods("PUT") dimension := dimension.Store{Storer: api.dataStore.Backend} - api.router.HandleFunc("/instances/{id}/dimensions", api.privateAuth.Check(dimension.GetNodes)).Methods("GET") - api.router.HandleFunc("/instances/{id}/dimensions", api.privateAuth.Check(instancePublishChecker.Check(dimension.Add))).Methods("POST") - api.router.HandleFunc("/instances/{id}/dimensions/{dimension}/options", api.privateAuth.Check(dimension.GetUnique)).Methods("GET") + api.router.HandleFunc("/instances/{id}/dimensions", identity.Check(dimension.GetNodes)).Methods("GET") + api.router.HandleFunc("/instances/{id}/dimensions", identity.Check(instancePublishChecker.Check(dimension.Add))).Methods("POST") + api.router.HandleFunc("/instances/{id}/dimensions/{dimension}/options", identity.Check(dimension.GetUnique)).Methods("GET") api.router.HandleFunc("/instances/{id}/dimensions/{dimension}/options/{value}/node_id/{node_id}", - api.privateAuth.Check(instancePublishChecker.Check(dimension.AddNodeID))).Methods("PUT") + identity.Check(instancePublishChecker.Check(dimension.AddNodeID))).Methods("PUT") } return &api } diff --git a/api/dataset.go b/api/dataset.go index cdaa6a23..13ca3c6c 100644 --- a/api/dataset.go +++ b/api/dataset.go @@ -16,7 +16,11 @@ import ( ) const ( - internalToken = "Internal-Token" + florenceHeaderKey = "X-Florence-Token" + authHeaderKey = "Authorization" + userIdentityKey = "User-Identity" + callerIdentityKey = "Caller-Identity" + datasetDocType = "dataset" editionDocType = "edition" versionDocType = "version" @@ -25,6 +29,14 @@ const ( dimensionOptionDocType = "dimension-option" ) +// type userIdentity string +// type callerIdentity string +// +// var ( +// contextKeyUserIdentity userIdentity = userIdentityKey +// contextKeyCallerIdentity callerIdentity = callerIdentityKey +// ) + func (api *DatasetAPI) getDatasets(w http.ResponseWriter, r *http.Request) { results, err := api.dataStore.Backend.GetDatasets() if err != nil { @@ -36,7 +48,14 @@ func (api *DatasetAPI) getDatasets(w http.ResponseWriter, r *http.Request) { var bytes []byte logData := log.Data{} - if api.EnablePrePublishView && r.Header.Get(internalToken) == api.internalToken { + var authorised bool + identity := "" + if api.EnablePrePublishView { + identity, authorised = authenticate(r) + } + logData["identity"] = identity + + if authorised { logData["authenticated"] = true datasets := &models.DatasetUpdateResults{} datasets.Items = results @@ -79,8 +98,15 @@ func (api *DatasetAPI) getDataset(w http.ResponseWriter, r *http.Request) { return } + var authorised bool + identity := "" + if api.EnablePrePublishView { + identity, authorised = authenticate(r) + } + logData["identity"] = identity + var bytes []byte - if !api.EnablePrePublishView || r.Header.Get(internalToken) != api.internalToken { + if !authorised { logData["authenticated"] = false if dataset.Current == nil { log.Debug("published dataset not found", nil) @@ -123,10 +149,17 @@ func (api *DatasetAPI) getEditions(w http.ResponseWriter, r *http.Request) { id := vars["id"] logData := log.Data{"dataset_id": id} + var authorised bool + identity := "" + if api.EnablePrePublishView { + identity, authorised = authenticate(r) + } + logData["identity"] = identity + var state string if !api.EnablePrePublishView { state = models.PublishedState - } else if r.Header.Get(internalToken) != api.internalToken { + } else if !authorised { logData["authenticated"] = false state = models.PublishedState } else { @@ -167,10 +200,18 @@ func (api *DatasetAPI) getEdition(w http.ResponseWriter, r *http.Request) { id := vars["id"] editionID := vars["edition"] logData := log.Data{"dataset_id": id, "edition": editionID} + + var authorised bool + identity := "" + if api.EnablePrePublishView { + identity, authorised = authenticate(r) + } + logData["identity"] = identity + var state string if !api.EnablePrePublishView { state = models.PublishedState - } else if r.Header.Get(internalToken) != api.internalToken { + } else if !authorised { logData["authenticated"] = false state = models.PublishedState } else { @@ -212,10 +253,17 @@ func (api *DatasetAPI) getVersions(w http.ResponseWriter, r *http.Request) { editionID := vars["edition"] logData := log.Data{"dataset_id": id, "edition": editionID} + var authorised bool + identity := "" + if api.EnablePrePublishView { + identity, authorised = authenticate(r) + } + logData["identity"] = identity + var state string if !api.EnablePrePublishView { state = models.PublishedState - } else if r.Header.Get(internalToken) != api.internalToken { + } else if !authorised { logData["authenticated"] = false state = models.PublishedState } else { @@ -291,11 +339,18 @@ func (api *DatasetAPI) getVersion(w http.ResponseWriter, r *http.Request) { editionID := vars["edition"] version := vars["version"] logData := log.Data{"dataset_id": id, "edition": editionID, "version": version} - var state string + var authorised bool + identity := "" + if api.EnablePrePublishView { + identity, authorised = authenticate(r) + } + logData["identity"] = identity + + var state string if !api.EnablePrePublishView { state = models.PublishedState - } else if r.Header.Get(internalToken) != api.internalToken { + } else if !authorised { logData["authenticated"] = false state = models.PublishedState } else { @@ -708,10 +763,18 @@ func (api *DatasetAPI) getDimensions(w http.ResponseWriter, r *http.Request) { edition := vars["edition"] version := vars["version"] logData := log.Data{"dataset_id": datasetID, "edition": edition, "version": version} + + var authorised bool + identity := "" + if api.EnablePrePublishView { + identity, authorised = authenticate(r) + } + logData["identity"] = identity + var state string if !api.EnablePrePublishView { state = models.PublishedState - } else if r.Header.Get(internalToken) != api.internalToken { + } else if !authorised { logData["authenticated"] = false state = models.PublishedState } else { @@ -816,10 +879,18 @@ func (api *DatasetAPI) getDimensionOptions(w http.ResponseWriter, r *http.Reques dimension := vars["dimension"] logData := log.Data{"dataset_id": datasetID, "edition": editionID, "version": versionID, "dimension": dimension} + + var authorised bool + identity := "" + if api.EnablePrePublishView { + identity, authorised = authenticate(r) + } + logData["identity"] = identity + var state string if !api.EnablePrePublishView { state = models.PublishedState - } else if r.Header.Get(internalToken) != api.internalToken { + } else if !authorised { logData["authenticated"] = false state = models.PublishedState } else { @@ -878,11 +949,18 @@ func (api *DatasetAPI) getMetadata(w http.ResponseWriter, r *http.Request) { return } + var authorised bool + identity := "" + if api.EnablePrePublishView { + identity, authorised = authenticate(r) + } + logData["identity"] = identity + // Default state to published var state string // if request is authenticated then access resources of state other than published - if !api.EnablePrePublishView || r.Header.Get(internalToken) != api.internalToken { + if !authorised { logData["authenticated"] = false // Check for current sub document if datasetDoc.Current == nil || datasetDoc.Current.State != models.PublishedState { @@ -1070,3 +1148,15 @@ func (d *PublishCheck) Check(handle func(http.ResponseWriter, *http.Request)) ht func setJSONContentType(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") } + +func authenticate(r *http.Request) (string, bool) { + callerIdentity, ok := r.Context().Value(callerIdentityKey).(string) + if ok && callerIdentity != "" { + return callerIdentity, true + } + userIdentity, ok := r.Context().Value(userIdentityKey).(string) + if ok && userIdentity != "" { + return userIdentity, true + } + return "", false +} diff --git a/api/dataset_test.go b/api/dataset_test.go index 45c071fb..399bec0b 100644 --- a/api/dataset_test.go +++ b/api/dataset_test.go @@ -2,8 +2,10 @@ package api import ( "bytes" + "context" "encoding/json" "errors" + "io" "io/ioutil" "net/http" "net/http/httptest" @@ -29,7 +31,7 @@ import ( const ( host = "http://localhost:22000" - secretKey = "coffee" + authToken = "dataset" healthTimeout = 2 * time.Second ) @@ -51,13 +53,21 @@ var ( func GetAPIWithMockedDatastore(mockedDataStore store.Storer, mockedGeneratedDownloads DownloadsGenerator) *DatasetAPI { cfg, err := config.Get() So(err, ShouldBeNil) - cfg.SecretKey = secretKey + cfg.ServiceAuthToken = authToken cfg.DatasetAPIURL = host cfg.EnablePrivateEnpoints = true cfg.HealthCheckTimeout = healthTimeout return routes(*cfg, mux.NewRouter(), store.DataStore{Backend: mockedDataStore}, urlBuilder, mockedGeneratedDownloads) } +func createRequestWithAuth(method, URL string, body io.Reader) (*http.Request, error) { + r, err := http.NewRequest(method, URL, body) + ctx := r.Context() + ctx = context.WithValue(ctx, callerIdentityKey, "someone@ons.gov.uk") + r = r.WithContext(ctx) + return r, err +} + func TestGetDatasetsReturnsOK(t *testing.T) { t.Parallel() Convey("A successful request to get dataset returns 200 OK response", t, func() { @@ -111,9 +121,10 @@ func TestGetDatasetReturnsOK(t *testing.T) { So(len(mockedDataStore.GetDatasetCalls()), ShouldEqual, 1) }) - Convey("When dataset document has only a next sub document and request contains valid internal_token return status 200", t, func() { - r := httptest.NewRequest("GET", "http://localhost:22000/datasets/123-456", nil) - r.Header.Add("internal-token", secretKey) + Convey("When dataset document has only a next sub document and request is authorised return status 200", t, func() { + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets/123-456", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(id string) (*models.DatasetUpdate, error) { @@ -746,8 +757,9 @@ func TestPostDatasetsReturnsCreated(t *testing.T) { Convey("A successful request to post dataset returns 200 OK response", t, func() { var b string b = datasetPayload - r := httptest.NewRequest("POST", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) - r.Header.Add("internal-token", secretKey) + r, err := createRequestWithAuth("POST", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(string) (*models.DatasetUpdate, error) { @@ -772,8 +784,9 @@ func TestPostDatasetReturnsError(t *testing.T) { Convey("When the request contain malformed json a bad request status is returned", t, func() { var b string b = "{" - r := httptest.NewRequest("POST", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) - r.Header.Add("internal-token", secretKey) + r, err := createRequestWithAuth("POST", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(string) (*models.DatasetUpdate, error) { @@ -796,8 +809,9 @@ func TestPostDatasetReturnsError(t *testing.T) { Convey("When the api cannot connect to datastore return an internal server error", t, func() { var b string b = datasetPayload - r := httptest.NewRequest("POST", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) - r.Header.Add("internal-token", secretKey) + r, err := createRequestWithAuth("POST", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(string) (*models.DatasetUpdate, error) { @@ -834,7 +848,7 @@ func TestPostDatasetReturnsError(t *testing.T) { api := GetAPIWithMockedDatastore(mockedDataStore, &mocks.DownloadsGeneratorMock{}) api.router.ServeHTTP(w, r) So(w.Code, ShouldEqual, http.StatusNotFound) - So(w.Body.String(), ShouldResemble, "Resource not found\n") + So(w.Body.String(), ShouldResemble, "requested resource not found\n") So(len(mockedDataStore.GetDatasetCalls()), ShouldEqual, 0) So(len(mockedDataStore.UpsertDatasetCalls()), ShouldEqual, 0) @@ -843,8 +857,9 @@ func TestPostDatasetReturnsError(t *testing.T) { Convey("When the dataset already exists and a request is sent to create the same dataset return status forbidden", t, func() { var b string b = datasetPayload - r := httptest.NewRequest("POST", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) - r.Header.Add("internal-token", secretKey) + r, err := createRequestWithAuth("POST", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(string) (*models.DatasetUpdate, error) { @@ -874,9 +889,9 @@ func TestPutDatasetReturnsSuccessfully(t *testing.T) { Convey("A successful request to put dataset returns 200 OK response", t, func() { var b string b = datasetPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(string) (*models.DatasetUpdate, error) { @@ -905,9 +920,9 @@ func TestPutDatasetReturnsError(t *testing.T) { Convey("When the request contain malformed json a bad request status is returned", t, func() { var b string b = "{" - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -931,9 +946,9 @@ func TestPutDatasetReturnsError(t *testing.T) { Convey("When the api cannot connect to datastore return an internal server error", t, func() { var b string b = versionPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -962,9 +977,9 @@ func TestPutDatasetReturnsError(t *testing.T) { Convey("When the dataset document cannot be found return status not found ", t, func() { var b string b = datasetPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -985,7 +1000,7 @@ func TestPutDatasetReturnsError(t *testing.T) { So(len(mockedDataStore.UpdateDatasetCalls()), ShouldEqual, 0) }) - Convey("When the request does not contain a valid internal token return status not found", t, func() { + Convey("When the request is not authorised to update dataset return status not found", t, func() { var b string b = "{\"edition\":\"2017\",\"state\":\"created\",\"license\":\"ONS\",\"release_date\":\"2017-04-04\",\"version\":\"1\"}" r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) @@ -1004,7 +1019,7 @@ func TestPutDatasetReturnsError(t *testing.T) { api := GetAPIWithMockedDatastore(mockedDataStore, &mocks.DownloadsGeneratorMock{}) api.router.ServeHTTP(w, r) So(w.Code, ShouldEqual, http.StatusNotFound) - So(w.Body.String(), ShouldResemble, "Resource not found\n") + So(w.Body.String(), ShouldResemble, "requested resource not found\n") So(len(mockedDataStore.GetDatasetCalls()), ShouldEqual, 0) So(len(mockedDataStore.UpdateDatasetCalls()), ShouldEqual, 0) @@ -1014,9 +1029,9 @@ func TestPutDatasetReturnsError(t *testing.T) { func TestDeleteDatasetReturnsSuccessfully(t *testing.T) { t.Parallel() Convey("A successful request to delete dataset returns 200 OK response", t, func() { - r, err := http.NewRequest("DELETE", "http://localhost:22000/datasets/123", nil) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("DELETE", "http://localhost:22000/datasets/123", nil) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(string) (*models.DatasetUpdate, error) { @@ -1035,12 +1050,12 @@ func TestDeleteDatasetReturnsSuccessfully(t *testing.T) { }) } -func TestDeleteDatasetOnPublishedReturnsForbidden(t *testing.T) { +func TestDeleteDatasetReturnsError(t *testing.T) { t.Parallel() - Convey("when a dataset is published it cannot be deleted", t, func() { - r, err := http.NewRequest("DELETE", "http://localhost:22000/datasets/123", nil) - r.Header.Add("internal-token", "coffee") + Convey("When a request to delete a published dataset return status forbidden", t, func() { + r, err := createRequestWithAuth("DELETE", "http://localhost:22000/datasets/123", nil) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(string) (*models.DatasetUpdate, error) { @@ -1057,15 +1072,11 @@ func TestDeleteDatasetOnPublishedReturnsForbidden(t *testing.T) { So(len(mockedDataStore.GetDatasetCalls()), ShouldEqual, 1) So(len(mockedDataStore.DeleteDatasetCalls()), ShouldEqual, 0) }) -} - -func TestDeleteDataset_DatastoreError(t *testing.T) { - t.Parallel() Convey("When the api cannot connect to datastore return an internal server error", t, func() { - r, err := http.NewRequest("DELETE", "http://localhost:22000/datasets/123", nil) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("DELETE", "http://localhost:22000/datasets/123", nil) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -1085,13 +1096,11 @@ func TestDeleteDataset_DatastoreError(t *testing.T) { So(len(mockedDataStore.GetDatasetCalls()), ShouldEqual, 1) So(len(mockedDataStore.DeleteDatasetCalls()), ShouldEqual, 1) }) -} -func TestDeleteDataset_NotFound(t *testing.T) { Convey("When the dataset document cannot be found return status not found ", t, func() { - r, err := http.NewRequest("DELETE", "http://localhost:22000/datasets/123", nil) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("DELETE", "http://localhost:22000/datasets/123", nil) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -1110,13 +1119,11 @@ func TestDeleteDataset_NotFound(t *testing.T) { So(len(mockedDataStore.GetDatasetCalls()), ShouldEqual, 1) So(len(mockedDataStore.UpdateDatasetCalls()), ShouldEqual, 0) }) -} -func TestDeleteDataset_GetDatasetError(t *testing.T) { Convey("When the dataset document cannot be queried return status 500 ", t, func() { - r, err := http.NewRequest("DELETE", "http://localhost:22000/datasets/123", nil) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("DELETE", "http://localhost:22000/datasets/123", nil) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -1135,14 +1142,13 @@ func TestDeleteDataset_GetDatasetError(t *testing.T) { So(len(mockedDataStore.GetDatasetCalls()), ShouldEqual, 1) So(len(mockedDataStore.UpdateDatasetCalls()), ShouldEqual, 0) }) -} -func TestDeleteDataset_InvalidToken(t *testing.T) { - Convey("When the request does not contain a valid internal token return status not found", t, func() { + Convey("When the request is not authorised to delete the dataset return status not found", t, func() { var b string b = "{\"edition\":\"2017\",\"state\":\"created\",\"license\":\"ONS\",\"release_date\":\"2017-04-04\",\"version\":\"1\"}" r, err := http.NewRequest("DELETE", "http://localhost:22000/datasets/123", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{} @@ -1150,7 +1156,7 @@ func TestDeleteDataset_InvalidToken(t *testing.T) { api := GetAPIWithMockedDatastore(mockedDataStore, &mocks.DownloadsGeneratorMock{}) api.router.ServeHTTP(w, r) So(w.Code, ShouldEqual, http.StatusNotFound) - So(w.Body.String(), ShouldResemble, "Resource not found\n") + So(w.Body.String(), ShouldResemble, "requested resource not found\n") So(len(mockedDataStore.GetDatasetCalls()), ShouldEqual, 0) So(len(mockedDataStore.DeleteDatasetCalls()), ShouldEqual, 0) @@ -1168,9 +1174,9 @@ func TestPutVersionReturnsSuccessfully(t *testing.T) { var b string b = versionPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -1233,9 +1239,9 @@ func TestPutVersionReturnsSuccessfully(t *testing.T) { var b string b = versionAssociatedPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -1287,9 +1293,9 @@ func TestPutVersionReturnsSuccessfully(t *testing.T) { var b string b = versionAssociatedPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -1344,9 +1350,9 @@ func TestPutVersionReturnsSuccessfully(t *testing.T) { var b string b = versionPublishedPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ @@ -1449,12 +1455,12 @@ func TestPutVersionGenerateDownloadsError(t *testing.T) { } Convey("when put version is called with a valid request", func() { - r, _ := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(versionAssociatedPayload)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(versionAssociatedPayload)) + So(err, ShouldBeNil) + w := httptest.NewRecorder() cfg, err := config.Get() So(err, ShouldBeNil) - cfg.SecretKey = secretKey cfg.EnablePrivateEnpoints = true api := routes(*cfg, mux.NewRouter(), store.DataStore{Backend: mockedDataStore}, urlBuilder, mockDownloadGenerator) api.router.ServeHTTP(w, r) @@ -1512,8 +1518,9 @@ func TestPutEmptyVersion(t *testing.T) { } Convey("when put version is called with an associated version with empty downloads", func() { - r, _ := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(versionAssociatedPayload)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(versionAssociatedPayload)) + So(err, ShouldBeNil) + w := httptest.NewRecorder() api := GetAPIWithMockedDatastore(mockedDataStore, &mocks.DownloadsGeneratorMock{}) @@ -1551,8 +1558,8 @@ func TestPutEmptyVersion(t *testing.T) { mockDownloadGenerator := &mocks.DownloadsGeneratorMock{} Convey("when put version is called with an associated version with empty downloads", func() { - r, _ := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(versionAssociatedPayload)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(versionAssociatedPayload)) + So(err, ShouldBeNil) w := httptest.NewRecorder() api := GetAPIWithMockedDatastore(mockedDataStore, &mocks.DownloadsGeneratorMock{}) @@ -1601,9 +1608,9 @@ func TestPutVersionReturnsError(t *testing.T) { var b string b = "{" - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetVersionFunc: func(string, string, string, string) (*models.Version, error) { @@ -1633,9 +1640,9 @@ func TestPutVersionReturnsError(t *testing.T) { var b string b = versionPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetVersionFunc: func(string, string, string, string) (*models.Version, error) { @@ -1665,9 +1672,9 @@ func TestPutVersionReturnsError(t *testing.T) { var b string b = versionPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetVersionFunc: func(string, string, string, string) (*models.Version, error) { @@ -1701,9 +1708,9 @@ func TestPutVersionReturnsError(t *testing.T) { var b string b = versionPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetVersionFunc: func(string, string, string, string) (*models.Version, error) { @@ -1737,9 +1744,9 @@ func TestPutVersionReturnsError(t *testing.T) { var b string b = versionPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetVersionFunc: func(string, string, string, string) (*models.Version, error) { @@ -1768,7 +1775,7 @@ func TestPutVersionReturnsError(t *testing.T) { So(len(generatorMock.GenerateCalls()), ShouldEqual, 0) }) - Convey("When the request does not contain a valid internal token return status not found", t, func() { + Convey("When the request is not authorised to update version then response returns status not found", t, func() { generatorMock := &mocks.DownloadsGeneratorMock{ GenerateFunc: func(string, string, string, string) error { return nil @@ -1791,7 +1798,7 @@ func TestPutVersionReturnsError(t *testing.T) { api := GetAPIWithMockedDatastore(mockedDataStore, generatorMock) api.router.ServeHTTP(w, r) So(w.Code, ShouldEqual, http.StatusNotFound) - So(w.Body.String(), ShouldEqual, "Resource not found\n") + So(w.Body.String(), ShouldEqual, "requested resource not found\n") So(len(mockedDataStore.GetVersionCalls()), ShouldEqual, 0) So(len(generatorMock.GenerateCalls()), ShouldEqual, 0) @@ -1806,9 +1813,9 @@ func TestPutVersionReturnsError(t *testing.T) { var b string b = versionPayload - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetVersionFunc: func(string, string, string, string) (*models.Version, error) { @@ -1839,9 +1846,9 @@ func TestPutVersionReturnsError(t *testing.T) { var b string b = `{"instance_id":"a1b2c3","edition":"2017","license":"ONS","release_date":"2017-04-04","state":"associated"}` - r, err := http.NewRequest("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) - r.Header.Add("internal-token", "coffee") + r, err := createRequestWithAuth("PUT", "http://localhost:22000/datasets/123/editions/2017/versions/1", bytes.NewBufferString(b)) So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetVersionFunc: func(string, string, string, string) (*models.Version, error) { @@ -2111,9 +2118,10 @@ func TestGetMetadataReturnsOk(t *testing.T) { datasetDoc := createDatasetDoc() versionDoc := createVersionDoc() - r := httptest.NewRequest("GET", "http://localhost:22000/datasets/123/editions/2017/versions/1/metadata", nil) + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets/123/editions/2017/versions/1/metadata", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() - r.Header.Add("Internal-Token", "coffee") mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(datasetID string) (*models.DatasetUpdate, error) { diff --git a/api/webendpoints_test.go b/api/webendpoints_test.go index 21e2060b..0c7568fb 100644 --- a/api/webendpoints_test.go +++ b/api/webendpoints_test.go @@ -1,13 +1,13 @@ package api import ( + "encoding/json" + "io/ioutil" "net/http" "net/http/httptest" "testing" - "io/ioutil" - - "encoding/json" + "gopkg.in/mgo.v2/bson" "github.com/ONSdigital/dp-dataset-api/config" "github.com/ONSdigital/dp-dataset-api/mocks" @@ -16,7 +16,6 @@ import ( storetest "github.com/ONSdigital/dp-dataset-api/store/datastoretest" "github.com/gorilla/mux" . "github.com/smartystreets/goconvey/convey" - "gopkg.in/mgo.v2/bson" ) // The follow unit tests check that when ENABLE_PRIVATE_ENDPOINTS is set to false, only @@ -29,8 +28,9 @@ func TestWebSubnetDatasetsEndpoint(t *testing.T) { next := &models.Dataset{ID: "4321", Title: "next"} Convey("When the API is started with private endpoints disabled", t, func() { - r := httptest.NewRequest("GET", "http://localhost:22000/datasets", nil) - r.Header.Add(internalToken, secretKey) + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetsFunc: func() ([]models.DatasetUpdate, error) { @@ -62,8 +62,9 @@ func TestWebSubnetDatasetEndpoint(t *testing.T) { next := &models.Dataset{ID: "1234", Title: "next"} Convey("When the API is started with private endpoints disabled", t, func() { - r := httptest.NewRequest("GET", "http://localhost:22000/datasets/1234", nil) - r.Header.Add(internalToken, secretKey) + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets/1234", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetDatasetFunc: func(ID string) (*models.DatasetUpdate, error) { @@ -93,8 +94,9 @@ func TestWebSubnetEditionsEndpoint(t *testing.T) { var editionSearchState, datasetSearchState string Convey("When the API is started with private endpoints disabled", t, func() { - r := httptest.NewRequest("GET", "http://localhost:22000/datasets/1234/editions", nil) - r.Header.Add(internalToken, secretKey) + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets/1234/editions", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ CheckDatasetExistsFunc: func(ID, state string) error { @@ -125,8 +127,9 @@ func TestWebSubnetEditionEndpoint(t *testing.T) { var editionSearchState, datasetSearchState string Convey("When the API is started with private endpoints disabled", t, func() { - r := httptest.NewRequest("GET", "http://localhost:22000/datasets/1234/editions/1234", nil) - r.Header.Add(internalToken, secretKey) + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets/1234/editions/1234", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ CheckDatasetExistsFunc: func(ID, state string) error { @@ -154,8 +157,9 @@ func TestWebSubnetVersionsEndpoint(t *testing.T) { var versionSearchState, editionSearchState, datasetSearchState string Convey("When the API is started with private endpoints disabled", t, func() { - r := httptest.NewRequest("GET", "http://localhost:22000/datasets/1234/editions/1234/versions", nil) - r.Header.Add(internalToken, secretKey) + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets/1234/editions/1234/versions", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ CheckDatasetExistsFunc: func(ID, state string) error { @@ -190,8 +194,9 @@ func TestWebSubnetVersionEndpoint(t *testing.T) { var versionSearchState, editionSearchState, datasetSearchState string Convey("When the API is started with private endpoints disabled", t, func() { - r := httptest.NewRequest("GET", "http://localhost:22000/datasets/1234/editions/1234/versions/1234", nil) - r.Header.Add(internalToken, secretKey) + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets/1234/editions/1234/versions/1234", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ CheckDatasetExistsFunc: func(ID, state string) error { @@ -228,8 +233,9 @@ func TestWebSubnetDimensionsEndpoint(t *testing.T) { var versionSearchState string Convey("When the API is started with private endpoints disabled", t, func() { - r := httptest.NewRequest("GET", "http://localhost:22000/datasets/1234/editions/1234/versions/1234/dimensions", nil) - r.Header.Add(internalToken, secretKey) + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets/1234/editions/1234/versions/1234/dimensions", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetVersionFunc: func(id string, editionID, version string, state string) (*models.Version, error) { @@ -258,8 +264,9 @@ func TestWebSubnetDimensionOptionsEndpoint(t *testing.T) { var versionSearchState string Convey("When the API is started with private endpoints disabled", t, func() { - r := httptest.NewRequest("GET", "http://localhost:22000/datasets/1234/editions/1234/versions/1234/dimensions/t/options", nil) - r.Header.Add(internalToken, secretKey) + r, err := createRequestWithAuth("GET", "http://localhost:22000/datasets/1234/editions/1234/versions/1234/dimensions/t/options", nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{ GetVersionFunc: func(id string, editionID, version string, state string) (*models.Version, error) { @@ -318,8 +325,9 @@ func TestPublishedSubnetEndpointsAreDisabled(t *testing.T) { for _, endpoint := range publishSubnetEndpoints { Convey("The following endpoint "+endpoint.URL+"(Method:"+endpoint.Method+") should return 404", func() { - r := httptest.NewRequest(endpoint.Method, endpoint.URL, nil) - r.Header.Add(internalToken, secretKey) + r, err := createRequestWithAuth(endpoint.Method, endpoint.URL, nil) + So(err, ShouldBeNil) + w := httptest.NewRecorder() mockedDataStore := &storetest.StorerMock{} api := GetWebAPIWithMockedDatastore(mockedDataStore, &mocks.DownloadsGeneratorMock{}) @@ -333,7 +341,7 @@ func TestPublishedSubnetEndpointsAreDisabled(t *testing.T) { func GetWebAPIWithMockedDatastore(mockedDataStore store.Storer, mockedGeneratedDownloads DownloadsGenerator) *DatasetAPI { cfg, err := config.Get() So(err, ShouldBeNil) - cfg.SecretKey = secretKey + cfg.ServiceAuthToken = authToken cfg.DatasetAPIURL = host cfg.EnablePrivateEnpoints = false cfg.HealthCheckTimeout = healthTimeout diff --git a/auth/authentication.go b/auth/authentication.go deleted file mode 100644 index de68d2fc..00000000 --- a/auth/authentication.go +++ /dev/null @@ -1,45 +0,0 @@ -package auth - -import ( - "net/http" - - errs "github.com/ONSdigital/dp-dataset-api/apierrors" - "github.com/ONSdigital/go-ns/log" -) - -// Authenticator structure which holds the secret key for validating clients. This will be replaced in the future, after the `thin-slices` has been delivered -type Authenticator struct { - SecretKey string - HeaderName string -} - -// Check wraps a HTTP handle. If authentication fails an error code is returned else the HTTP handler is called -func (a *Authenticator) Check(handle func(http.ResponseWriter, *http.Request)) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - key := r.Header.Get(a.HeaderName) - if key == "" { - http.Error(w, "Resource not found", http.StatusNotFound) - log.Error(errs.ErrNoAuthHeader, log.Data{"header": a.HeaderName}) - return - } - if key != a.SecretKey { - http.Error(w, "Resource not found", http.StatusNotFound) - log.Error(errs.ErrUnauthorised, log.Data{"header": a.HeaderName}) - return - } - // The request has been authenticated, now run the clients request - handle(w, r) - }) -} - -// ManualCheck a boolean is set and passed to the HTTP handler, its the handler responsibility to set the status code -func (a *Authenticator) ManualCheck(handle func(http.ResponseWriter, *http.Request, bool)) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - isAuthenticated := false - key := r.Header.Get(a.HeaderName) - if key == a.SecretKey { - isAuthenticated = true - } - handle(w, r, isAuthenticated) - }) -} diff --git a/auth/authentication_test.go b/auth/authentication_test.go deleted file mode 100644 index 7627be52..00000000 --- a/auth/authentication_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package auth - -import ( - "net/http" - "net/http/httptest" - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -func TestMiddleWareAuthenticationReturnsForbidden(t *testing.T) { - t.Parallel() - Convey("When no access token is provide, unauthorised status code is returned", t, func() { - auth := &Authenticator{"123", "internal-token"} - r, err := http.NewRequest("POST", "http://localhost:21800/instances", nil) - So(err, ShouldBeNil) - w := httptest.NewRecorder() - auth.Check(mockHTTPHandler).ServeHTTP(w, r) - So(w.Code, ShouldEqual, http.StatusNotFound) - So(w.Body.String(), ShouldEqual, "Resource not found\n") - }) -} - -func TestMiddleWareAuthenticationReturnsUnauthorised(t *testing.T) { - t.Parallel() - Convey("When a invalid access token is provide, unauthorised status code is returned", t, func() { - auth := &Authenticator{"123", "internal-token"} - r, err := http.NewRequest("POST", "http://localhost:21800/instances", nil) - r.Header.Set("internal-token", "12") - So(err, ShouldBeNil) - w := httptest.NewRecorder() - auth.Check(mockHTTPHandler).ServeHTTP(w, r) - So(w.Code, ShouldEqual, http.StatusNotFound) - So(w.Body.String(), ShouldEqual, "Resource not found\n") - }) -} - -func TestMiddleWareAuthentication(t *testing.T) { - t.Parallel() - Convey("When a valid access token is provide, OK code is returned", t, func() { - auth := &Authenticator{"123", "internal-token"} - r, err := http.NewRequest("POST", "http://localhost:21800/instances", nil) - r.Header.Set("internal-token", "123") - So(err, ShouldBeNil) - w := httptest.NewRecorder() - auth.Check(mockHTTPHandler).ServeHTTP(w, r) - So(w.Code, ShouldEqual, http.StatusOK) - }) -} - -func TestMiddleWareAuthenticationWithValue(t *testing.T) { - t.Parallel() - Convey("When a valid access token is provide, true is passed to a http handler", t, func() { - auth := &Authenticator{"123", "internal-token"} - r, err := http.NewRequest("POST", "http://localhost:21800/instances", nil) - r.Header.Set("internal-token", "123") - So(err, ShouldBeNil) - w := httptest.NewRecorder() - var isRequestAuthenticated bool - auth.ManualCheck(func(w http.ResponseWriter, r *http.Request, isAuth bool) { - isRequestAuthenticated = isAuth - }).ServeHTTP(w, r) - So(w.Code, ShouldEqual, http.StatusOK) - So(isRequestAuthenticated, ShouldEqual, true) - }) -} - -func mockHTTPHandler(w http.ResponseWriter, r *http.Request) { - -} diff --git a/config/config.go b/config/config.go index 9ff24287..e969940c 100644 --- a/config/config.go +++ b/config/config.go @@ -14,9 +14,10 @@ type Configuration struct { GenerateDownloadsTopic string `envconfig:"GENERATE_DOWNLOADS_TOPIC"` CodeListAPIURL string `envconfig:"CODE_LIST_API_URL"` DatasetAPIURL string `envconfig:"DATASET_API_URL"` - DownloadServiceSecretKey string `envconfig:"DOWNLOAD_SERVICE_SECRET_KEY" json:"-"` WebsiteURL string `envconfig:"WEBSITE_URL"` - SecretKey string `envconfig:"SECRET_KEY" json:"-"` + ZebedeeURL string `envconfig:"ZEBEDEE_URL"` + DownloadServiceSecretKey string `envconfig:"DOWNLOAD_SERVICE_SECRET_KEY" json:"-"` + ServiceAuthToken string `envconfig:"SERVICE_AUTH_TOKEN" json:"-"` GracefulShutdownTimeout time.Duration `envconfig:"GRACEFUL_SHUTDOWN_TIMEOUT"` HealthCheckTimeout time.Duration `envconfig:"HEALTHCHECK_TIMEOUT"` EnablePrivateEnpoints bool `envconfig:"ENABLE_PRIVATE_ENDPOINTS"` @@ -45,7 +46,8 @@ func Get() (*Configuration, error) { CodeListAPIURL: "http://localhost:22400", DatasetAPIURL: "http://localhost:22000", WebsiteURL: "http://localhost:20000", - SecretKey: "FD0108EA-825D-411C-9B1D-41EF7727F465", + ZebedeeURL: "http://localhost:8082", + ServiceAuthToken: "FD0108EA-825D-411C-9B1D-41EF7727F465", DownloadServiceSecretKey: "QB0108EZ-825D-412C-9B1D-41EF7747F462", GracefulShutdownTimeout: 5 * time.Second, HealthCheckTimeout: 2 * time.Second, diff --git a/config/config_test.go b/config/config_test.go index 03ddee10..583ed27d 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -13,7 +13,7 @@ func TestSpec(t *testing.T) { Convey("When the config values are retrieved", func() { - Convey("There should be no error returned", func() { + Convey("Then there should be no error returned", func() { So(err, ShouldBeNil) }) @@ -25,7 +25,8 @@ func TestSpec(t *testing.T) { So(cfg.CodeListAPIURL, ShouldEqual, "http://localhost:22400") So(cfg.DownloadServiceSecretKey, ShouldEqual, "QB0108EZ-825D-412C-9B1D-41EF7747F462") So(cfg.WebsiteURL, ShouldEqual, "http://localhost:20000") - So(cfg.SecretKey, ShouldEqual, "FD0108EA-825D-411C-9B1D-41EF7727F465") + So(cfg.ZebedeeURL, ShouldEqual, "http://localhost:8082") + So(cfg.ServiceAuthToken, ShouldEqual, "FD0108EA-825D-411C-9B1D-41EF7727F465") So(cfg.GracefulShutdownTimeout, ShouldEqual, 5*time.Second) So(cfg.MongoConfig.BindAddr, ShouldEqual, "localhost:27017") So(cfg.MongoConfig.Collection, ShouldEqual, "datasets") diff --git a/vendor/github.com/ONSdigital/go-ns/identity/README.md b/vendor/github.com/ONSdigital/go-ns/identity/README.md new file mode 100644 index 00000000..a4aa559e --- /dev/null +++ b/vendor/github.com/ONSdigital/go-ns/identity/README.md @@ -0,0 +1,45 @@ +Identity middleware +=================== + +Middleware componenet that authenticates requests against zebedee. + +The identity and permissions returned from the identity endpoint are added to the request context. + +### Getting started + +Initialise the identity middleware and add it into the HTTP handler chain using alice: + +``` + router := mux.NewRouter() + alice := alice.New(identity.Handler(true)).Then(router) + httpServer := server.New(config.BindAddr, alice) +``` + +Wrap authenticated endpoints using the `identity.Check(handler)` function to check that a request identity exists. + +``` + router.Path("/jobs").Methods("POST").HandlerFunc(identity.Check(api.addJob)) +``` + +Add required headers to outbound requests to other services + +``` + req.Header.Add("Authorization", api.AuthToken) + req.Header.Add("User-Identity", ctx.Value("User-Identity").(string)) +``` + +### Testing + +If you need to use the middleware component in unit tests you can call the constructor function that allows injection of the HTTP client + +``` +httpClient := &identitytest.HttpClientMock{ + DoFunc: func(ctx context.Context, req *http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + }, nil + }, +} + +identityHandler := identity.HandlerForHttpClient(doAuth, httpClient) +``` \ No newline at end of file diff --git a/vendor/github.com/ONSdigital/go-ns/identity/authentication.go b/vendor/github.com/ONSdigital/go-ns/identity/authentication.go new file mode 100644 index 00000000..afb6f398 --- /dev/null +++ b/vendor/github.com/ONSdigital/go-ns/identity/authentication.go @@ -0,0 +1,31 @@ +package identity + +import ( + "errors" + "net/http" + + "github.com/ONSdigital/go-ns/log" +) + +// Check wraps a HTTP handler. If authentication fails an error code is returned else the HTTP handler is called +func Check(handle func(http.ResponseWriter, *http.Request)) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + log.DebugR(r, "checking for an identity in request context", nil) + + callerIdentity := r.Context().Value(callerIdentityKey) + logData := log.Data{ "caller_identity": callerIdentity } + + // just checking if an identity exists until permissions are being provided. + if callerIdentity == nil || callerIdentity == ""{ + http.Error(w, "requested resource not found", http.StatusNotFound) + log.ErrorR(r, errors.New("no identity was found in the context of this request"), logData) + return + } + + log.DebugR(r, "identity found in request context, calling downstream handler", logData) + + // The request has been authenticated, now run the clients request + handle(w, r) + }) +} diff --git a/vendor/github.com/ONSdigital/go-ns/identity/identity.go b/vendor/github.com/ONSdigital/go-ns/identity/identity.go new file mode 100644 index 00000000..977d4f6b --- /dev/null +++ b/vendor/github.com/ONSdigital/go-ns/identity/identity.go @@ -0,0 +1,130 @@ +package identity + +import ( + "net/http" + "github.com/ONSdigital/go-ns/rchttp" + + "context" + "github.com/ONSdigital/go-ns/log" + "encoding/json" + "io/ioutil" +) + +//go:generate moq -out identitytest/http_client.go -pkg identitytest . HttpClient + +const florenceHeaderKey = "X-Florence-Token" +const authHeaderKey = "Authorization" +const userIdentityKey = "User-Identity" +const callerIdentityKey = "Caller-Identity" + +type HttpClient interface { + Do(ctx context.Context, req *http.Request) (*http.Response, error) +} + +type identityResponse struct { + Identifier string `json:"identifier"` +} + +// Handler controls the authenticating of a request +func Handler(doAuth bool, zebedeeURL string) func(http.Handler) http.Handler { + return HandlerForHttpClient(doAuth, rchttp.DefaultClient, zebedeeURL) +} + +// HandlerForHttpClient allows a handler to be created that uses the given HTTP client +func HandlerForHttpClient(doAuth bool, cli HttpClient, zebedeeURL string) func(http.Handler) http.Handler { + return func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + + logData := log.Data{} + log.DebugR(req, "identity middleware called", logData) + + ctx := req.Context() + + if doAuth { + + florenceToken := req.Header.Get(florenceHeaderKey) + authToken := req.Header.Get(authHeaderKey) + + isUserReq := len(florenceToken) > 0 + isServiceReq := len(authToken) > 0 + + logData["is_user_request"] = isUserReq + logData["is_service_request"] = isServiceReq + + log.DebugR(req, "authentication enabled, checking for expected tokens", logData) + + if isUserReq || isServiceReq { + + url := zebedeeURL + "/identity" + + logData["url"] = url + log.DebugR(req, "calling zebedee to authenticate", logData) + + zebReq, err := http.NewRequest("GET", url, nil) + if err != nil { + log.ErrorR(req, err, logData) + w.WriteHeader(http.StatusInternalServerError) + return + } + + if isUserReq { + zebReq.Header.Set(florenceHeaderKey, florenceToken) + } else { + zebReq.Header.Set(authHeaderKey, authToken) + } + + resp, err := cli.Do(ctx, zebReq) + if err != nil { + log.ErrorR(req, err, logData) + w.WriteHeader(http.StatusInternalServerError) + return + } + + // Check to see if the user is authorised + if resp.StatusCode != http.StatusOK { + logData["status"] = resp.StatusCode + log.DebugR(req, "unexpected status code returned from zebedee identity endpoint", logData) + w.WriteHeader(resp.StatusCode) + return + } + + identityResp, err := unmarshalResponse(resp) + if err != nil { + log.ErrorR(req, err, logData) + w.WriteHeader(http.StatusInternalServerError) + return + } + + userIdentity := identityResp.Identifier + if !isUserReq { + userIdentity = req.Header.Get(userIdentityKey) + } + + logData["user_identity"] = userIdentity + logData["caller_identity"] = identityResp.Identifier + log.DebugR(req, "user identity retrieved, setting context values", logData) + + ctx = context.WithValue(ctx, userIdentityKey, userIdentity) + ctx = context.WithValue(ctx, callerIdentityKey, identityResp.Identifier) + } + } else { + log.DebugR(req, "skipping authentication against zebedee, auth is not enabled", nil) + } + + log.DebugR(req, "identity middleware finished, calling downstream handler", logData) + + h.ServeHTTP(w, req.WithContext(ctx)) + }) + } +} + +func unmarshalResponse(resp *http.Response) (identityResp *identityResponse, err error) { + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return identityResp, err + } + defer resp.Body.Close() + + return identityResp, json.Unmarshal(b, &identityResp) +} diff --git a/vendor/github.com/ONSdigital/go-ns/rchttp/README.md b/vendor/github.com/ONSdigital/go-ns/rchttp/README.md new file mode 100644 index 00000000..ea2945b1 --- /dev/null +++ b/vendor/github.com/ONSdigital/go-ns/rchttp/README.md @@ -0,0 +1,60 @@ +# rchttp + +rchttp stands for robust contextual HTTP, and provides a default client +that inherits the methods associated with the standard HTTP client, +but with the addition of production-ready timeouts and context-sensitivity, +and the ability to perform exponential backoff when calling another HTTP server. + +### How to use + +rchttp should have a familiar feel to it when it is used - with an example given +below: + +```go +import "github.com/ONSdigital/go-ns/rchttp" + +func httpHandlerFunc(w http.ResponseWriter, req *http.Request) { + client := rchttp.DefaultClient + + resp, err := rcClient.Get(req.Context(), "https://www.google.com") + if err != nil { + fmt.Println(err) + return + } +} +``` + +In this case, in the unlikely event of https://www.google.com returning a status +of 500 or above, the client will retry at exponentially-increasing intervals, until +the max retries (10 by default is reached). + +Also, if the inbound request is cancelled, for example, its context will be closed +and this will be noticed by the client. + +You also do not have to use the default client if you don't like the configured +timeouts or do not wish to use exponential backoff. The following example shows +how to configure your own rchttp client: + +```go +import "github.com/ONSdigital/go-ns/rchttp" + +func main() { + rcClient := &rchttp.Client{ + MaxRetries: 10, // The maximum number of retries you wish to wait for + ExponentialBackoff: true, // Set to false if you do not want exponential backoff + RetryTime: 1 * time.Second, // The time between the first set of retries + + HTTPClient: &http.Client{ // Create your own http client with configured timeouts + Timeout: 10 * time.Second, + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 5 * time.Second, + }).DialContext, + TLSHandshakeTimeout: 5 * time.Second, + MaxIdleConns: 10, + IdleConnTimeout: 30 * time.Second, + }, + }, + } +} +``` diff --git a/vendor/github.com/ONSdigital/go-ns/rchttp/client.go b/vendor/github.com/ONSdigital/go-ns/rchttp/client.go new file mode 100644 index 00000000..024863b0 --- /dev/null +++ b/vendor/github.com/ONSdigital/go-ns/rchttp/client.go @@ -0,0 +1,161 @@ +package rchttp + +import ( + "io" + "math" + "math/rand" + "net" + "net/http" + "net/url" + "strings" + "time" + + "golang.org/x/net/context" + "golang.org/x/net/context/ctxhttp" +) + +// Client is an extension of the net/http client with ability to add +// timeouts, exponential backoff and context-based cancellation +type Client struct { + MaxRetries int + ExponentialBackoff bool + RetryTime time.Duration + HTTPClient *http.Client +} + +// DefaultClient is a go-ns specific http client with sensible timeouts, +// exponential backoff, and a contextual dialer +var DefaultClient = &Client{ + MaxRetries: 10, + ExponentialBackoff: true, + RetryTime: 20 * time.Millisecond, + + HTTPClient: &http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + DialContext: (&net.Dialer{ + Timeout: 5 * time.Second, + }).DialContext, + TLSHandshakeTimeout: 5 * time.Second, + MaxIdleConns: 10, + IdleConnTimeout: 30 * time.Second, + }, + }, +} + +// ClientWithTimeout facilitates creating a client and setting request timeout +func ClientWithTimeout(timeout time.Duration) (c *Client) { + c = DefaultClient + c.HTTPClient.Timeout = timeout + return c +} + +// Do calls ctxhttp.Do with the addition of exponential backoff +func (c *Client) Do(ctx context.Context, req *http.Request) (*http.Response, error) { + doer := func(args ...interface{}) (*http.Response, error) { + req := args[2].(*http.Request) + if req.ContentLength > 0 { + var err error + req.Body, err = req.GetBody() + if err != nil { + return nil, err + } + } + return ctxhttp.Do(args[0].(context.Context), args[1].(*http.Client), req) + } + + resp, err := doer(ctx, c.HTTPClient, req) + if err != nil { + if c.ExponentialBackoff { + return c.backoff(doer, ctx, c.HTTPClient, req) + } + return nil, err + } + + if c.ExponentialBackoff && resp.StatusCode >= http.StatusInternalServerError { + return c.backoff(doer, ctx, c.HTTPClient, req) + } + + return resp, err +} + +// Get calls Do with a GET +func (c *Client) Get(ctx context.Context, url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + return c.Do(ctx, req) +} + +// Head calls Do with a HEAD +func (c *Client) Head(ctx context.Context, url string) (*http.Response, error) { + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return nil, err + } + + return c.Do(ctx, req) +} + +// Post calls Do with a POST and the appropriate content-type and body +func (c *Client) Post(ctx context.Context, url string, contentType string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", contentType) + + return c.Do(ctx, req) +} + +// Put calls Do with a PUT and the appropriate content-type and body +func (c *Client) Put(ctx context.Context, url string, contentType string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest("PUT", url, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", contentType) + + return c.Do(ctx, req) +} + +// PostForm calls Post with the appropriate form content-type +func (c *Client) PostForm(ctx context.Context, uri string, data url.Values) (*http.Response, error) { + return c.Post(ctx, uri, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} + +func (c *Client) backoff(f func(...interface{}) (*http.Response, error), args ...interface{}) (resp *http.Response, err error) { + for attempt := 1; attempt <= c.MaxRetries; attempt++ { + // ensure that the context is not cancelled before iterating + if args[0].(context.Context).Err() != nil { + err = args[0].(context.Context).Err() + return + } + + time.Sleep(getSleepTime(attempt, c.RetryTime)) + + resp, err = f(args...) + // prioritise any context cancellation + if args[0].(context.Context).Err() != nil { + err = args[0].(context.Context).Err() + return + } + if err == nil && resp.StatusCode < http.StatusInternalServerError { + return + } + } + return +} + +// getSleepTime will return a sleep time based on the attempt and initial retry time. +// It uses the algorithm 2^n where n is the attempt number (double the previous) and +// a randomization factor of between 0-5ms so that the server isn't being hit constantly +// at the same time by many clients +func getSleepTime(attempt int, retryTime time.Duration) time.Duration { + n := (math.Pow(2, float64(attempt))) + rand.Seed(time.Now().Unix()) + rnd := time.Duration(rand.Intn(4)+1) * time.Millisecond + return (time.Duration(n) * retryTime) - rnd +} diff --git a/vendor/golang.org/x/net/LICENSE b/vendor/golang.org/x/net/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/vendor/golang.org/x/net/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/net/PATENTS b/vendor/golang.org/x/net/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/vendor/golang.org/x/net/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/net/context/context.go b/vendor/golang.org/x/net/context/context.go new file mode 100644 index 00000000..a3c021d3 --- /dev/null +++ b/vendor/golang.org/x/net/context/context.go @@ -0,0 +1,56 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package context defines the Context type, which carries deadlines, +// cancelation signals, and other request-scoped values across API boundaries +// and between processes. +// As of Go 1.7 this package is available in the standard library under the +// name context. https://golang.org/pkg/context. +// +// Incoming requests to a server should create a Context, and outgoing calls to +// servers should accept a Context. The chain of function calls between must +// propagate the Context, optionally replacing it with a modified copy created +// using WithDeadline, WithTimeout, WithCancel, or WithValue. +// +// Programs that use Contexts should follow these rules to keep interfaces +// consistent across packages and enable static analysis tools to check context +// propagation: +// +// Do not store Contexts inside a struct type; instead, pass a Context +// explicitly to each function that needs it. The Context should be the first +// parameter, typically named ctx: +// +// func DoSomething(ctx context.Context, arg Arg) error { +// // ... use ctx ... +// } +// +// Do not pass a nil Context, even if a function permits it. Pass context.TODO +// if you are unsure about which Context to use. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +// +// The same Context may be passed to functions running in different goroutines; +// Contexts are safe for simultaneous use by multiple goroutines. +// +// See http://blog.golang.org/context for example code for a server that uses +// Contexts. +package context // import "golang.org/x/net/context" + +// Background returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming +// requests. +func Background() Context { + return background +} + +// TODO returns a non-nil, empty Context. Code should use context.TODO when +// it's unclear which Context to use or it is not yet available (because the +// surrounding function has not yet been extended to accept a Context +// parameter). TODO is recognized by static analysis tools that determine +// whether Contexts are propagated correctly in a program. +func TODO() Context { + return todo +} diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go new file mode 100644 index 00000000..606cf1f9 --- /dev/null +++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go @@ -0,0 +1,74 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7 + +// Package ctxhttp provides helper functions for performing context-aware HTTP requests. +package ctxhttp // import "golang.org/x/net/context/ctxhttp" + +import ( + "io" + "net/http" + "net/url" + "strings" + + "golang.org/x/net/context" +) + +// Do sends an HTTP request with the provided http.Client and returns +// an HTTP response. +// +// If the client is nil, http.DefaultClient is used. +// +// The provided ctx must be non-nil. If it is canceled or times out, +// ctx.Err() will be returned. +func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { + if client == nil { + client = http.DefaultClient + } + resp, err := client.Do(req.WithContext(ctx)) + // If we got an error, and the context has been canceled, + // the context's error is probably more useful. + if err != nil { + select { + case <-ctx.Done(): + err = ctx.Err() + default: + } + } + return resp, err +} + +// Get issues a GET request via the Do function. +func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Head issues a HEAD request via the Do function. +func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Post issues a POST request via the Do function. +func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", bodyType) + return Do(ctx, client, req) +} + +// PostForm issues a POST request via the Do function. +func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) { + return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go new file mode 100644 index 00000000..926870cc --- /dev/null +++ b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp_pre17.go @@ -0,0 +1,147 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.7 + +package ctxhttp // import "golang.org/x/net/context/ctxhttp" + +import ( + "io" + "net/http" + "net/url" + "strings" + + "golang.org/x/net/context" +) + +func nop() {} + +var ( + testHookContextDoneBeforeHeaders = nop + testHookDoReturned = nop + testHookDidBodyClose = nop +) + +// Do sends an HTTP request with the provided http.Client and returns an HTTP response. +// If the client is nil, http.DefaultClient is used. +// If the context is canceled or times out, ctx.Err() will be returned. +func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { + if client == nil { + client = http.DefaultClient + } + + // TODO(djd): Respect any existing value of req.Cancel. + cancel := make(chan struct{}) + req.Cancel = cancel + + type responseAndError struct { + resp *http.Response + err error + } + result := make(chan responseAndError, 1) + + // Make local copies of test hooks closed over by goroutines below. + // Prevents data races in tests. + testHookDoReturned := testHookDoReturned + testHookDidBodyClose := testHookDidBodyClose + + go func() { + resp, err := client.Do(req) + testHookDoReturned() + result <- responseAndError{resp, err} + }() + + var resp *http.Response + + select { + case <-ctx.Done(): + testHookContextDoneBeforeHeaders() + close(cancel) + // Clean up after the goroutine calling client.Do: + go func() { + if r := <-result; r.resp != nil { + testHookDidBodyClose() + r.resp.Body.Close() + } + }() + return nil, ctx.Err() + case r := <-result: + var err error + resp, err = r.resp, r.err + if err != nil { + return resp, err + } + } + + c := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + close(cancel) + case <-c: + // The response's Body is closed. + } + }() + resp.Body = ¬ifyingReader{resp.Body, c} + + return resp, nil +} + +// Get issues a GET request via the Do function. +func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Head issues a HEAD request via the Do function. +func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) { + req, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return nil, err + } + return Do(ctx, client, req) +} + +// Post issues a POST request via the Do function. +func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) { + req, err := http.NewRequest("POST", url, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", bodyType) + return Do(ctx, client, req) +} + +// PostForm issues a POST request via the Do function. +func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) { + return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) +} + +// notifyingReader is an io.ReadCloser that closes the notify channel after +// Close is called or a Read fails on the underlying ReadCloser. +type notifyingReader struct { + io.ReadCloser + notify chan<- struct{} +} + +func (r *notifyingReader) Read(p []byte) (int, error) { + n, err := r.ReadCloser.Read(p) + if err != nil && r.notify != nil { + close(r.notify) + r.notify = nil + } + return n, err +} + +func (r *notifyingReader) Close() error { + err := r.ReadCloser.Close() + if r.notify != nil { + close(r.notify) + r.notify = nil + } + return err +} diff --git a/vendor/golang.org/x/net/context/go17.go b/vendor/golang.org/x/net/context/go17.go new file mode 100644 index 00000000..d20f52b7 --- /dev/null +++ b/vendor/golang.org/x/net/context/go17.go @@ -0,0 +1,72 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.7 + +package context + +import ( + "context" // standard library's context, as of Go 1.7 + "time" +) + +var ( + todo = context.TODO() + background = context.Background() +) + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = context.Canceled + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = context.DeadlineExceeded + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + ctx, f := context.WithCancel(parent) + return ctx, CancelFunc(f) +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + ctx, f := context.WithDeadline(parent, deadline) + return ctx, CancelFunc(f) +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return context.WithValue(parent, key, val) +} diff --git a/vendor/golang.org/x/net/context/go19.go b/vendor/golang.org/x/net/context/go19.go new file mode 100644 index 00000000..d88bd1db --- /dev/null +++ b/vendor/golang.org/x/net/context/go19.go @@ -0,0 +1,20 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.9 + +package context + +import "context" // standard library's context, as of Go 1.7 + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context = context.Context + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc = context.CancelFunc diff --git a/vendor/golang.org/x/net/context/pre_go17.go b/vendor/golang.org/x/net/context/pre_go17.go new file mode 100644 index 00000000..0f35592d --- /dev/null +++ b/vendor/golang.org/x/net/context/pre_go17.go @@ -0,0 +1,300 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.7 + +package context + +import ( + "errors" + "fmt" + "sync" + "time" +) + +// An emptyCtx is never canceled, has no values, and has no deadline. It is not +// struct{}, since vars of this type must have distinct addresses. +type emptyCtx int + +func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { + return +} + +func (*emptyCtx) Done() <-chan struct{} { + return nil +} + +func (*emptyCtx) Err() error { + return nil +} + +func (*emptyCtx) Value(key interface{}) interface{} { + return nil +} + +func (e *emptyCtx) String() string { + switch e { + case background: + return "context.Background" + case todo: + return "context.TODO" + } + return "unknown empty Context" +} + +var ( + background = new(emptyCtx) + todo = new(emptyCtx) +) + +// Canceled is the error returned by Context.Err when the context is canceled. +var Canceled = errors.New("context canceled") + +// DeadlineExceeded is the error returned by Context.Err when the context's +// deadline passes. +var DeadlineExceeded = errors.New("context deadline exceeded") + +// WithCancel returns a copy of parent with a new Done channel. The returned +// context's Done channel is closed when the returned cancel function is called +// or when the parent context's Done channel is closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { + c := newCancelCtx(parent) + propagateCancel(parent, c) + return c, func() { c.cancel(true, Canceled) } +} + +// newCancelCtx returns an initialized cancelCtx. +func newCancelCtx(parent Context) *cancelCtx { + return &cancelCtx{ + Context: parent, + done: make(chan struct{}), + } +} + +// propagateCancel arranges for child to be canceled when parent is. +func propagateCancel(parent Context, child canceler) { + if parent.Done() == nil { + return // parent is never canceled + } + if p, ok := parentCancelCtx(parent); ok { + p.mu.Lock() + if p.err != nil { + // parent has already been canceled + child.cancel(false, p.err) + } else { + if p.children == nil { + p.children = make(map[canceler]bool) + } + p.children[child] = true + } + p.mu.Unlock() + } else { + go func() { + select { + case <-parent.Done(): + child.cancel(false, parent.Err()) + case <-child.Done(): + } + }() + } +} + +// parentCancelCtx follows a chain of parent references until it finds a +// *cancelCtx. This function understands how each of the concrete types in this +// package represents its parent. +func parentCancelCtx(parent Context) (*cancelCtx, bool) { + for { + switch c := parent.(type) { + case *cancelCtx: + return c, true + case *timerCtx: + return c.cancelCtx, true + case *valueCtx: + parent = c.Context + default: + return nil, false + } + } +} + +// removeChild removes a context from its parent. +func removeChild(parent Context, child canceler) { + p, ok := parentCancelCtx(parent) + if !ok { + return + } + p.mu.Lock() + if p.children != nil { + delete(p.children, child) + } + p.mu.Unlock() +} + +// A canceler is a context type that can be canceled directly. The +// implementations are *cancelCtx and *timerCtx. +type canceler interface { + cancel(removeFromParent bool, err error) + Done() <-chan struct{} +} + +// A cancelCtx can be canceled. When canceled, it also cancels any children +// that implement canceler. +type cancelCtx struct { + Context + + done chan struct{} // closed by the first cancel call. + + mu sync.Mutex + children map[canceler]bool // set to nil by the first cancel call + err error // set to non-nil by the first cancel call +} + +func (c *cancelCtx) Done() <-chan struct{} { + return c.done +} + +func (c *cancelCtx) Err() error { + c.mu.Lock() + defer c.mu.Unlock() + return c.err +} + +func (c *cancelCtx) String() string { + return fmt.Sprintf("%v.WithCancel", c.Context) +} + +// cancel closes c.done, cancels each of c's children, and, if +// removeFromParent is true, removes c from its parent's children. +func (c *cancelCtx) cancel(removeFromParent bool, err error) { + if err == nil { + panic("context: internal error: missing cancel error") + } + c.mu.Lock() + if c.err != nil { + c.mu.Unlock() + return // already canceled + } + c.err = err + close(c.done) + for child := range c.children { + // NOTE: acquiring the child's lock while holding parent's lock. + child.cancel(false, err) + } + c.children = nil + c.mu.Unlock() + + if removeFromParent { + removeChild(c.Context, c) + } +} + +// WithDeadline returns a copy of the parent context with the deadline adjusted +// to be no later than d. If the parent's deadline is already earlier than d, +// WithDeadline(parent, d) is semantically equivalent to parent. The returned +// context's Done channel is closed when the deadline expires, when the returned +// cancel function is called, or when the parent context's Done channel is +// closed, whichever happens first. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { + if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { + // The current deadline is already sooner than the new one. + return WithCancel(parent) + } + c := &timerCtx{ + cancelCtx: newCancelCtx(parent), + deadline: deadline, + } + propagateCancel(parent, c) + d := deadline.Sub(time.Now()) + if d <= 0 { + c.cancel(true, DeadlineExceeded) // deadline has already passed + return c, func() { c.cancel(true, Canceled) } + } + c.mu.Lock() + defer c.mu.Unlock() + if c.err == nil { + c.timer = time.AfterFunc(d, func() { + c.cancel(true, DeadlineExceeded) + }) + } + return c, func() { c.cancel(true, Canceled) } +} + +// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to +// implement Done and Err. It implements cancel by stopping its timer then +// delegating to cancelCtx.cancel. +type timerCtx struct { + *cancelCtx + timer *time.Timer // Under cancelCtx.mu. + + deadline time.Time +} + +func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { + return c.deadline, true +} + +func (c *timerCtx) String() string { + return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) +} + +func (c *timerCtx) cancel(removeFromParent bool, err error) { + c.cancelCtx.cancel(false, err) + if removeFromParent { + // Remove this timerCtx from its parent cancelCtx's children. + removeChild(c.cancelCtx.Context, c) + } + c.mu.Lock() + if c.timer != nil { + c.timer.Stop() + c.timer = nil + } + c.mu.Unlock() +} + +// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)). +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete: +// +// func slowOperationWithTimeout(ctx context.Context) (Result, error) { +// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) +// defer cancel() // releases resources if slowOperation completes before timeout elapses +// return slowOperation(ctx) +// } +func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { + return WithDeadline(parent, time.Now().Add(timeout)) +} + +// WithValue returns a copy of parent in which the value associated with key is +// val. +// +// Use context Values only for request-scoped data that transits processes and +// APIs, not for passing optional parameters to functions. +func WithValue(parent Context, key interface{}, val interface{}) Context { + return &valueCtx{parent, key, val} +} + +// A valueCtx carries a key-value pair. It implements Value for that key and +// delegates all other calls to the embedded Context. +type valueCtx struct { + Context + key, val interface{} +} + +func (c *valueCtx) String() string { + return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) +} + +func (c *valueCtx) Value(key interface{}) interface{} { + if c.key == key { + return c.val + } + return c.Context.Value(key) +} diff --git a/vendor/golang.org/x/net/context/pre_go19.go b/vendor/golang.org/x/net/context/pre_go19.go new file mode 100644 index 00000000..b105f80b --- /dev/null +++ b/vendor/golang.org/x/net/context/pre_go19.go @@ -0,0 +1,109 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.9 + +package context + +import "time" + +// A Context carries a deadline, a cancelation signal, and other values across +// API boundaries. +// +// Context's methods may be called by multiple goroutines simultaneously. +type Context interface { + // Deadline returns the time when work done on behalf of this context + // should be canceled. Deadline returns ok==false when no deadline is + // set. Successive calls to Deadline return the same results. + Deadline() (deadline time.Time, ok bool) + + // Done returns a channel that's closed when work done on behalf of this + // context should be canceled. Done may return nil if this context can + // never be canceled. Successive calls to Done return the same value. + // + // WithCancel arranges for Done to be closed when cancel is called; + // WithDeadline arranges for Done to be closed when the deadline + // expires; WithTimeout arranges for Done to be closed when the timeout + // elapses. + // + // Done is provided for use in select statements: + // + // // Stream generates values with DoSomething and sends them to out + // // until DoSomething returns an error or ctx.Done is closed. + // func Stream(ctx context.Context, out chan<- Value) error { + // for { + // v, err := DoSomething(ctx) + // if err != nil { + // return err + // } + // select { + // case <-ctx.Done(): + // return ctx.Err() + // case out <- v: + // } + // } + // } + // + // See http://blog.golang.org/pipelines for more examples of how to use + // a Done channel for cancelation. + Done() <-chan struct{} + + // Err returns a non-nil error value after Done is closed. Err returns + // Canceled if the context was canceled or DeadlineExceeded if the + // context's deadline passed. No other values for Err are defined. + // After Done is closed, successive calls to Err return the same value. + Err() error + + // Value returns the value associated with this context for key, or nil + // if no value is associated with key. Successive calls to Value with + // the same key returns the same result. + // + // Use context values only for request-scoped data that transits + // processes and API boundaries, not for passing optional parameters to + // functions. + // + // A key identifies a specific value in a Context. Functions that wish + // to store values in Context typically allocate a key in a global + // variable then use that key as the argument to context.WithValue and + // Context.Value. A key can be any type that supports equality; + // packages should define keys as an unexported type to avoid + // collisions. + // + // Packages that define a Context key should provide type-safe accessors + // for the values stores using that key: + // + // // Package user defines a User type that's stored in Contexts. + // package user + // + // import "golang.org/x/net/context" + // + // // User is the type of value stored in the Contexts. + // type User struct {...} + // + // // key is an unexported type for keys defined in this package. + // // This prevents collisions with keys defined in other packages. + // type key int + // + // // userKey is the key for user.User values in Contexts. It is + // // unexported; clients use user.NewContext and user.FromContext + // // instead of using this key directly. + // var userKey key = 0 + // + // // NewContext returns a new Context that carries value u. + // func NewContext(ctx context.Context, u *User) context.Context { + // return context.WithValue(ctx, userKey, u) + // } + // + // // FromContext returns the User value stored in ctx, if any. + // func FromContext(ctx context.Context) (*User, bool) { + // u, ok := ctx.Value(userKey).(*User) + // return u, ok + // } + Value(key interface{}) interface{} +} + +// A CancelFunc tells an operation to abandon its work. +// A CancelFunc does not wait for the work to stop. +// After the first call, subsequent calls to a CancelFunc do nothing. +type CancelFunc func() diff --git a/vendor/vendor.json b/vendor/vendor.json index 3f93a968..bc049f57 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -14,6 +14,12 @@ "revision": "6920413b753350672215a083e0f9d5c270a21075", "revisionTime": "2017-11-28T09:28:02Z" }, + { + "checksumSHA1": "k3zGZN2MUA0bjGqkG2BT0x5Nx70=", + "path": "github.com/ONSdigital/go-ns/identity", + "revision": "98b5dee4d793d5a0547b7bc7d18aceb353c88a54", + "revisionTime": "2018-03-18T14:57:33Z" + }, { "checksumSHA1": "d0xkjPw9SKVWK1vDJqvD3HoFbnA=", "path": "github.com/ONSdigital/go-ns/kafka", @@ -32,6 +38,12 @@ "revision": "6920413b753350672215a083e0f9d5c270a21075", "revisionTime": "2017-11-28T09:28:02Z" }, + { + "checksumSHA1": "eqT3lUe+1W+njYJOealHQ+LsXv0=", + "path": "github.com/ONSdigital/go-ns/rchttp", + "revision": "98b5dee4d793d5a0547b7bc7d18aceb353c88a54", + "revisionTime": "2018-03-18T14:57:33Z" + }, { "checksumSHA1": "GQdzMpAMb42KQQ/GsJFSRU5dj1Y=", "path": "github.com/ONSdigital/go-ns/server", @@ -207,6 +219,20 @@ "revision": "e5b2b7c9111590d019a696c7800593f666e1a7f4", "revisionTime": "2017-08-25T22:14:26Z" }, + { + "checksumSHA1": "GtamqiJoL7PGHsN454AoffBFMa8=", + "origin": "github.com/ONSdigital/go-ns/vendor/golang.org/x/net/context", + "path": "golang.org/x/net/context", + "revision": "98b5dee4d793d5a0547b7bc7d18aceb353c88a54", + "revisionTime": "2018-03-18T14:57:33Z" + }, + { + "checksumSHA1": "WHc3uByvGaMcnSoI21fhzYgbOgg=", + "origin": "github.com/ONSdigital/go-ns/vendor/golang.org/x/net/context/ctxhttp", + "path": "golang.org/x/net/context/ctxhttp", + "revision": "98b5dee4d793d5a0547b7bc7d18aceb353c88a54", + "revisionTime": "2018-03-18T14:57:33Z" + }, { "checksumSHA1": "1D8GzeoFGUs5FZOoyC2DpQg8c5Y=", "path": "gopkg.in/mgo.v2", From 86c4cf2a5e16f209dda4088e289a16d113ee8d5c Mon Sep 17 00:00:00 2001 From: nshumoogum Date: Thu, 22 Mar 2018 11:11:25 +0000 Subject: [PATCH 2/8] Rework dataset api authenticate function for GET endpoints Instead of returning either callerIdentity or userIdentity, return both; this is for scenarios where the dataset API is called from another service. --- api/dataset.go | 131 ++++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 66 deletions(-) diff --git a/api/dataset.go b/api/dataset.go index c1f4bd92..97886c17 100644 --- a/api/dataset.go +++ b/api/dataset.go @@ -41,12 +41,13 @@ func (api *DatasetAPI) getDatasets(w http.ResponseWriter, r *http.Request) { logData := log.Data{} var authorised bool - identity := "" + var callerIdentity, userIdentity string if api.EnablePrePublishView { - identity, authorised = authenticate(r) - logData["authenticated"] = authorised + callerIdentity, userIdentity, authorised = authenticate(r) + logData["caller_identity"] = callerIdentity + logData["user_identity"] = userIdentity } - logData["identity"] = identity + logData["authenticated"] = authorised if authorised { @@ -94,12 +95,13 @@ func (api *DatasetAPI) getDataset(w http.ResponseWriter, r *http.Request) { } var authorised bool - identity := "" + var callerIdentity, userIdentity string if api.EnablePrePublishView { - identity, authorised = authenticate(r) - logData["authenticated"] = authorised + callerIdentity, userIdentity, authorised = authenticate(r) + logData["caller_identity"] = callerIdentity + logData["user_identity"] = userIdentity } - logData["identity"] = identity + logData["authenticated"] = authorised var b []byte if !authorised { @@ -146,12 +148,13 @@ func (api *DatasetAPI) getEditions(w http.ResponseWriter, r *http.Request) { logData := log.Data{"dataset_id": id} var authorised bool - identity := "" + var callerIdentity, userIdentity string if api.EnablePrePublishView { - identity, authorised = authenticate(r) - logData["authenticated"] = authorised + callerIdentity, userIdentity, authorised = authenticate(r) + logData["caller_identity"] = callerIdentity + logData["user_identity"] = userIdentity } - logData["identity"] = identity + logData["authenticated"] = authorised var state string if !authorised { @@ -221,12 +224,13 @@ func (api *DatasetAPI) getEdition(w http.ResponseWriter, r *http.Request) { logData := log.Data{"dataset_id": id, "edition": editionID} var authorised bool - identity := "" + var callerIdentity, userIdentity string if api.EnablePrePublishView { - identity, authorised = authenticate(r) - logData["authenticated"] = authorised + callerIdentity, userIdentity, authorised = authenticate(r) + logData["caller_identity"] = callerIdentity + logData["user_identity"] = userIdentity } - logData["identity"] = identity + logData["authenticated"] = authorised var state string if !authorised { @@ -288,19 +292,17 @@ func (api *DatasetAPI) getVersions(w http.ResponseWriter, r *http.Request) { logData := log.Data{"dataset_id": id, "edition": editionID} var authorised bool - identity := "" + var callerIdentity, userIdentity string if api.EnablePrePublishView { - identity, authorised = authenticate(r) - logData["authenticated"] = authorised + callerIdentity, userIdentity, authorised = authenticate(r) + logData["caller_identity"] = callerIdentity + logData["user_identity"] = userIdentity } - logData["identity"] = identity + logData["authenticated"] = authorised var state string - if !api.EnablePrePublishView { - state = models.PublishedState - } else if !authorised { + if !authorised { state = models.PublishedState - } else { } if err := api.dataStore.Backend.CheckDatasetExists(id, state); err != nil { @@ -374,20 +376,17 @@ func (api *DatasetAPI) getVersion(w http.ResponseWriter, r *http.Request) { logData := log.Data{"dataset_id": id, "edition": editionID, "version": version} var authorised bool - identity := "" + var callerIdentity, userIdentity string if api.EnablePrePublishView { - identity, authorised = authenticate(r) + callerIdentity, userIdentity, authorised = authenticate(r) + logData["caller_identity"] = callerIdentity + logData["user_identity"] = userIdentity } - logData["identity"] = identity + logData["authenticated"] = authorised var state string - if !api.EnablePrePublishView { - state = models.PublishedState - } else if !authorised { - logData["authenticated"] = false + if !authorised { state = models.PublishedState - } else { - logData["authenticated"] = true } if err := api.dataStore.Backend.CheckDatasetExists(id, state); err != nil { @@ -809,20 +808,17 @@ func (api *DatasetAPI) getDimensions(w http.ResponseWriter, r *http.Request) { logData := log.Data{"dataset_id": datasetID, "edition": edition, "version": version} var authorised bool - identity := "" + var callerIdentity, userIdentity string if api.EnablePrePublishView { - identity, authorised = authenticate(r) + callerIdentity, userIdentity, authorised = authenticate(r) + logData["caller_identity"] = callerIdentity + logData["user_identity"] = userIdentity } - logData["identity"] = identity + logData["authenticated"] = authorised var state string - if !api.EnablePrePublishView { - state = models.PublishedState - } else if !authorised { - logData["authenticated"] = false + if !authorised { state = models.PublishedState - } else { - logData["authenticated"] = true } versionDoc, err := api.dataStore.Backend.GetVersion(datasetID, edition, version, state) @@ -925,20 +921,17 @@ func (api *DatasetAPI) getDimensionOptions(w http.ResponseWriter, r *http.Reques logData := log.Data{"dataset_id": datasetID, "edition": editionID, "version": versionID, "dimension": dimension} var authorised bool - identity := "" + var callerIdentity, userIdentity string if api.EnablePrePublishView { - identity, authorised = authenticate(r) + callerIdentity, userIdentity, authorised = authenticate(r) + logData["caller_identity"] = callerIdentity + logData["user_identity"] = userIdentity } - logData["identity"] = identity + logData["authenticated"] = authorised var state string - if !api.EnablePrePublishView { - state = models.PublishedState - } else if !authorised { - logData["authenticated"] = false + if !authorised { state = models.PublishedState - } else { - logData["authenticated"] = true } version, err := api.dataStore.Backend.GetVersion(datasetID, editionID, versionID, state) @@ -994,18 +987,18 @@ func (api *DatasetAPI) getMetadata(w http.ResponseWriter, r *http.Request) { } var authorised bool - identity := "" + var callerIdentity, userIdentity string if api.EnablePrePublishView { - identity, authorised = authenticate(r) + callerIdentity, userIdentity, authorised = authenticate(r) + logData["caller_identity"] = callerIdentity + logData["user_identity"] = userIdentity } - logData["identity"] = identity + logData["authenticated"] = authorised - // Default state to published var state string // if request is authenticated then access resources of state other than published if !authorised { - logData["authenticated"] = false // Check for current sub document if datasetDoc.Current == nil || datasetDoc.Current.State != models.PublishedState { log.ErrorC("found dataset but currently unpublished", errs.ErrDatasetNotFound, log.Data{"dataset_id": datasetID, "edition": edition, "version": version, "dataset": datasetDoc.Current}) @@ -1014,8 +1007,6 @@ func (api *DatasetAPI) getMetadata(w http.ResponseWriter, r *http.Request) { } state = datasetDoc.Current.State - } else { - logData["authenticated"] = true } if err = api.dataStore.Backend.CheckEditionExists(datasetID, edition, state); err != nil { @@ -1193,14 +1184,22 @@ func setJSONContentType(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") } -func authenticate(r *http.Request) (string, bool) { - callerIdentity, ok := r.Context().Value(callerIdentityKey).(string) - if ok && callerIdentity != "" { - return callerIdentity, true +func authenticate(r *http.Request) (callerIdentity, userIdentity string, authorised bool) { + var callerIdentityOk, userIdentityOk, hasCallerIdentity, hasUserIdentity bool + + callerIdentity, callerIdentityOk = r.Context().Value(callerIdentityKey).(string) + if callerIdentityOk && callerIdentity != "" { + hasCallerIdentity = true } - userIdentity, ok := r.Context().Value(userIdentityKey).(string) - if ok && userIdentity != "" { - return userIdentity, true + + userIdentity, userIdentityOk = r.Context().Value(userIdentityKey).(string) + if userIdentityOk && userIdentity != "" { + hasUserIdentity = true + } + + if hasCallerIdentity || hasUserIdentity { + authorised = true } - return "", false + + return } From 9c58f34e55b00cd81b49e7d3c05c46356ca54464 Mon Sep 17 00:00:00 2001 From: nshumoogum Date: Thu, 22 Mar 2018 12:01:59 +0000 Subject: [PATCH 3/8] Use go-ns identity Caller and User functions This simplifies the application knowledge of the keys stored in context --- api/dataset.go | 13 ++++----- api/dataset_test.go | 2 +- .../go-ns/identity/authentication.go | 28 +++++++++++++++++-- .../ONSdigital/go-ns/identity/identity.go | 4 ++- vendor/vendor.json | 6 ++-- 5 files changed, 39 insertions(+), 14 deletions(-) diff --git a/api/dataset.go b/api/dataset.go index 97886c17..7a6b16a7 100644 --- a/api/dataset.go +++ b/api/dataset.go @@ -9,6 +9,7 @@ import ( errs "github.com/ONSdigital/dp-dataset-api/apierrors" "github.com/ONSdigital/dp-dataset-api/models" "github.com/ONSdigital/dp-dataset-api/store" + "github.com/ONSdigital/go-ns/identity" "github.com/ONSdigital/go-ns/log" "github.com/gorilla/mux" "github.com/pkg/errors" @@ -18,8 +19,6 @@ import ( const ( florenceHeaderKey = "X-Florence-Token" authHeaderKey = "Authorization" - userIdentityKey = "User-Identity" - callerIdentityKey = "Caller-Identity" datasetDocType = "dataset" editionDocType = "edition" @@ -1185,15 +1184,15 @@ func setJSONContentType(w http.ResponseWriter) { } func authenticate(r *http.Request) (callerIdentity, userIdentity string, authorised bool) { - var callerIdentityOk, userIdentityOk, hasCallerIdentity, hasUserIdentity bool + var hasCallerIdentity, hasUserIdentity bool - callerIdentity, callerIdentityOk = r.Context().Value(callerIdentityKey).(string) - if callerIdentityOk && callerIdentity != "" { + callerIdentity = identity.Caller(r.Context()) + if callerIdentity != "" { hasCallerIdentity = true } - userIdentity, userIdentityOk = r.Context().Value(userIdentityKey).(string) - if userIdentityOk && userIdentity != "" { + userIdentity = identity.User(r.Context()) + if userIdentity != "" { hasUserIdentity = true } diff --git a/api/dataset_test.go b/api/dataset_test.go index cb77fac5..e1cb6cce 100644 --- a/api/dataset_test.go +++ b/api/dataset_test.go @@ -63,7 +63,7 @@ func GetAPIWithMockedDatastore(mockedDataStore store.Storer, mockedGeneratedDown func createRequestWithAuth(method, URL string, body io.Reader) (*http.Request, error) { r, err := http.NewRequest(method, URL, body) ctx := r.Context() - ctx = context.WithValue(ctx, callerIdentityKey, "someone@ons.gov.uk") + ctx = context.WithValue(ctx, "Caller-Identity", "someone@ons.gov.uk") r = r.WithContext(ctx) return r, err } diff --git a/vendor/github.com/ONSdigital/go-ns/identity/authentication.go b/vendor/github.com/ONSdigital/go-ns/identity/authentication.go index afb6f398..ccfbfaf5 100644 --- a/vendor/github.com/ONSdigital/go-ns/identity/authentication.go +++ b/vendor/github.com/ONSdigital/go-ns/identity/authentication.go @@ -5,6 +5,7 @@ import ( "net/http" "github.com/ONSdigital/go-ns/log" + "context" ) // Check wraps a HTTP handler. If authentication fails an error code is returned else the HTTP handler is called @@ -14,10 +15,10 @@ func Check(handle func(http.ResponseWriter, *http.Request)) http.HandlerFunc { log.DebugR(r, "checking for an identity in request context", nil) callerIdentity := r.Context().Value(callerIdentityKey) - logData := log.Data{ "caller_identity": callerIdentity } + logData := log.Data{"caller_identity": callerIdentity} // just checking if an identity exists until permissions are being provided. - if callerIdentity == nil || callerIdentity == ""{ + if !IsPresent(r.Context()) { http.Error(w, "requested resource not found", http.StatusNotFound) log.ErrorR(r, errors.New("no identity was found in the context of this request"), logData) return @@ -29,3 +30,26 @@ func Check(handle func(http.ResponseWriter, *http.Request)) http.HandlerFunc { handle(w, r) }) } + +// IsPresent determines if an identity is present on the given context. +func IsPresent(ctx context.Context) bool { + + callerIdentity := ctx.Value(callerIdentityKey) + isPresent := callerIdentity != nil && callerIdentity != "" + + return isPresent +} + +// Caller gets the caller identity from the context +func Caller(ctx context.Context) (string) { + + callerIdentity, _ := ctx.Value(callerIdentityKey).(string) + return callerIdentity +} + +// User gets the user identity from the context +func User(ctx context.Context) (string) { + + userIdentity, _ := ctx.Value(userIdentityKey).(string) + return userIdentity +} diff --git a/vendor/github.com/ONSdigital/go-ns/identity/identity.go b/vendor/github.com/ONSdigital/go-ns/identity/identity.go index 977d4f6b..64826486 100644 --- a/vendor/github.com/ONSdigital/go-ns/identity/identity.go +++ b/vendor/github.com/ONSdigital/go-ns/identity/identity.go @@ -2,12 +2,14 @@ package identity import ( "net/http" + "github.com/ONSdigital/go-ns/rchttp" "context" - "github.com/ONSdigital/go-ns/log" "encoding/json" "io/ioutil" + + "github.com/ONSdigital/go-ns/log" ) //go:generate moq -out identitytest/http_client.go -pkg identitytest . HttpClient diff --git a/vendor/vendor.json b/vendor/vendor.json index bc049f57..dff2a670 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -15,10 +15,10 @@ "revisionTime": "2017-11-28T09:28:02Z" }, { - "checksumSHA1": "k3zGZN2MUA0bjGqkG2BT0x5Nx70=", + "checksumSHA1": "8WZ7eKoEMQj4Cq8OGQwIilow/ik=", "path": "github.com/ONSdigital/go-ns/identity", - "revision": "98b5dee4d793d5a0547b7bc7d18aceb353c88a54", - "revisionTime": "2018-03-18T14:57:33Z" + "revision": "779d639e4a0c116b62484d10275cffb4940ee804", + "revisionTime": "2018-03-22T11:27:23Z" }, { "checksumSHA1": "d0xkjPw9SKVWK1vDJqvD3HoFbnA=", From b09757daa6477f5b0aa0c58d69375182d0f7df40 Mon Sep 17 00:00:00 2001 From: nshumoogum Date: Thu, 22 Mar 2018 14:10:57 +0000 Subject: [PATCH 4/8] Move enable prepublish view check to authentication method To reduce duplication of code when authenticating on GET endpoints --- api/dataset.go | 121 +++++++++++++------------------------------------ 1 file changed, 32 insertions(+), 89 deletions(-) diff --git a/api/dataset.go b/api/dataset.go index 7a6b16a7..41b4aa87 100644 --- a/api/dataset.go +++ b/api/dataset.go @@ -36,18 +36,9 @@ func (api *DatasetAPI) getDatasets(w http.ResponseWriter, r *http.Request) { return } - var b []byte - logData := log.Data{} - - var authorised bool - var callerIdentity, userIdentity string - if api.EnablePrePublishView { - callerIdentity, userIdentity, authorised = authenticate(r) - logData["caller_identity"] = callerIdentity - logData["user_identity"] = userIdentity - } - logData["authenticated"] = authorised + authorised, logData := api.authenticate(r, log.Data{}) + var b []byte if authorised { // User has valid authentication to get raw dataset document @@ -93,14 +84,7 @@ func (api *DatasetAPI) getDataset(w http.ResponseWriter, r *http.Request) { return } - var authorised bool - var callerIdentity, userIdentity string - if api.EnablePrePublishView { - callerIdentity, userIdentity, authorised = authenticate(r) - logData["caller_identity"] = callerIdentity - logData["user_identity"] = userIdentity - } - logData["authenticated"] = authorised + authorised, logData := api.authenticate(r, logData) var b []byte if !authorised { @@ -146,14 +130,7 @@ func (api *DatasetAPI) getEditions(w http.ResponseWriter, r *http.Request) { id := vars["id"] logData := log.Data{"dataset_id": id} - var authorised bool - var callerIdentity, userIdentity string - if api.EnablePrePublishView { - callerIdentity, userIdentity, authorised = authenticate(r) - logData["caller_identity"] = callerIdentity - logData["user_identity"] = userIdentity - } - logData["authenticated"] = authorised + authorised, logData := api.authenticate(r, logData) var state string if !authorised { @@ -222,14 +199,7 @@ func (api *DatasetAPI) getEdition(w http.ResponseWriter, r *http.Request) { editionID := vars["edition"] logData := log.Data{"dataset_id": id, "edition": editionID} - var authorised bool - var callerIdentity, userIdentity string - if api.EnablePrePublishView { - callerIdentity, userIdentity, authorised = authenticate(r) - logData["caller_identity"] = callerIdentity - logData["user_identity"] = userIdentity - } - logData["authenticated"] = authorised + authorised, logData := api.authenticate(r, logData) var state string if !authorised { @@ -290,14 +260,7 @@ func (api *DatasetAPI) getVersions(w http.ResponseWriter, r *http.Request) { editionID := vars["edition"] logData := log.Data{"dataset_id": id, "edition": editionID} - var authorised bool - var callerIdentity, userIdentity string - if api.EnablePrePublishView { - callerIdentity, userIdentity, authorised = authenticate(r) - logData["caller_identity"] = callerIdentity - logData["user_identity"] = userIdentity - } - logData["authenticated"] = authorised + authorised, logData := api.authenticate(r, logData) var state string if !authorised { @@ -374,14 +337,7 @@ func (api *DatasetAPI) getVersion(w http.ResponseWriter, r *http.Request) { version := vars["version"] logData := log.Data{"dataset_id": id, "edition": editionID, "version": version} - var authorised bool - var callerIdentity, userIdentity string - if api.EnablePrePublishView { - callerIdentity, userIdentity, authorised = authenticate(r) - logData["caller_identity"] = callerIdentity - logData["user_identity"] = userIdentity - } - logData["authenticated"] = authorised + authorised, logData := api.authenticate(r, logData) var state string if !authorised { @@ -806,14 +762,7 @@ func (api *DatasetAPI) getDimensions(w http.ResponseWriter, r *http.Request) { version := vars["version"] logData := log.Data{"dataset_id": datasetID, "edition": edition, "version": version} - var authorised bool - var callerIdentity, userIdentity string - if api.EnablePrePublishView { - callerIdentity, userIdentity, authorised = authenticate(r) - logData["caller_identity"] = callerIdentity - logData["user_identity"] = userIdentity - } - logData["authenticated"] = authorised + authorised, logData := api.authenticate(r, logData) var state string if !authorised { @@ -919,14 +868,7 @@ func (api *DatasetAPI) getDimensionOptions(w http.ResponseWriter, r *http.Reques logData := log.Data{"dataset_id": datasetID, "edition": editionID, "version": versionID, "dimension": dimension} - var authorised bool - var callerIdentity, userIdentity string - if api.EnablePrePublishView { - callerIdentity, userIdentity, authorised = authenticate(r) - logData["caller_identity"] = callerIdentity - logData["user_identity"] = userIdentity - } - logData["authenticated"] = authorised + authorised, logData := api.authenticate(r, logData) var state string if !authorised { @@ -985,14 +927,7 @@ func (api *DatasetAPI) getMetadata(w http.ResponseWriter, r *http.Request) { return } - var authorised bool - var callerIdentity, userIdentity string - if api.EnablePrePublishView { - callerIdentity, userIdentity, authorised = authenticate(r) - logData["caller_identity"] = callerIdentity - logData["user_identity"] = userIdentity - } - logData["authenticated"] = authorised + authorised, logData := api.authenticate(r, logData) var state string @@ -1183,22 +1118,30 @@ func setJSONContentType(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json") } -func authenticate(r *http.Request) (callerIdentity, userIdentity string, authorised bool) { - var hasCallerIdentity, hasUserIdentity bool +func (api *DatasetAPI) authenticate(r *http.Request, logData map[string]interface{}) (bool, map[string]interface{}) { + var authorised bool - callerIdentity = identity.Caller(r.Context()) - if callerIdentity != "" { - hasCallerIdentity = true - } + if api.EnablePrePublishView { + var hasCallerIdentity, hasUserIdentity bool - userIdentity = identity.User(r.Context()) - if userIdentity != "" { - hasUserIdentity = true - } + callerIdentity := identity.Caller(r.Context()) + if callerIdentity != "" { + logData["caller_identity"] = callerIdentity + hasCallerIdentity = true + } - if hasCallerIdentity || hasUserIdentity { - authorised = true - } + userIdentity := identity.User(r.Context()) + if userIdentity != "" { + logData["user_identity"] = userIdentity + hasUserIdentity = true + } - return + if hasCallerIdentity || hasUserIdentity { + authorised = true + } + logData["authenticated"] = authorised + + return authorised, logData + } + return authorised, logData } From bdcfc10a2bac6d6968f19a491d84a1da854fa5f1 Mon Sep 17 00:00:00 2001 From: Lloyd Griffiths Date: Wed, 28 Mar 2018 10:38:26 +0100 Subject: [PATCH 5/8] add dockerfile --- Dockerfile.concourse | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Dockerfile.concourse diff --git a/Dockerfile.concourse b/Dockerfile.concourse new file mode 100644 index 00000000..005465f9 --- /dev/null +++ b/Dockerfile.concourse @@ -0,0 +1,7 @@ +FROM onsdigital/dp-concourse-tools-ubuntu + +WORKDIR /app/ + +ADD dp-dataset-api . + +CMD ./dp-dataset-api From 4e16fb4cac5e6ff8054048f6ad5df9b88de891c7 Mon Sep 17 00:00:00 2001 From: Lloyd Griffiths Date: Wed, 28 Mar 2018 10:38:51 +0100 Subject: [PATCH 6/8] copy out dockerfile --- ci/scripts/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/scripts/build.sh b/ci/scripts/build.sh index 4c005feb..e50f75e4 100755 --- a/ci/scripts/build.sh +++ b/ci/scripts/build.sh @@ -6,4 +6,5 @@ export GOPATH=$cwd/go pushd $GOPATH/src/github.com/ONSdigital/dp-dataset-api make build && mv build/$(go env GOOS)-$(go env GOARCH)/* $cwd/build + cp Dockerfile.concourse $cwd/build popd From f71d2c9e1be10f025a9252570fd8b0ad2c59cfd2 Mon Sep 17 00:00:00 2001 From: Carl Hembrough Date: Wed, 4 Apr 2018 16:15:17 +0100 Subject: [PATCH 7/8] Update readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 64a2d3a3..2c2772f9 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,8 @@ one of: | GENERATE_DOWNLOADS_TOPIC | "filter-job-submitted" | The topic to send generate full dataset version downloads to | HEALTHCHECK_TIMEOUT | 2s | The timeout that the healthcheck allows for checked subsystems | ENABLE_PRIVATE_ENDPOINTS | false | Enable private endpoints for the API -| DOWNLOAD_SERVICE_SECRET_KEY | "QB0108EZ-825D-412C-9B1D-41EF7747F462" | A key specific for the download service to access public/private links -| ZEBEDEE_URL | "http://localhost:8082" | The host name for Zebedee +| DOWNLOAD_SERVICE_SECRET_KEY | QB0108EZ-825D-412C-9B1D-41EF7747F462 | A key specific for the download service to access public/private links +| ZEBEDEE_URL | http://localhost:8082 | The host name for Zebedee ### Contributing From 8453c10c58b0c489bf07f1638460c11c5a96d905 Mon Sep 17 00:00:00 2001 From: Carl Hembrough Date: Thu, 5 Apr 2018 12:10:08 +0100 Subject: [PATCH 8/8] Do not add identity middleware when running in web --- api/api.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/api/api.go b/api/api.go index e0fe311a..9698518d 100644 --- a/api/api.go +++ b/api/api.go @@ -48,8 +48,14 @@ func CreateDatasetAPI(cfg config.Configuration, dataStore store.DataStore, urlBu router := mux.NewRouter() routes(cfg, router, dataStore, urlBuilder, downloadsGenerator) - alice := alice.New(identity.Handler(true, cfg.ZebedeeURL)).Then(router) - httpServer = server.New(cfg.BindAddr, alice) + // Only add the identity middleware when running in publishing. + if cfg.EnablePrivateEnpoints { + alice := alice.New(identity.Handler(true, cfg.ZebedeeURL)).Then(router) + httpServer = server.New(cfg.BindAddr, alice) + } else { + httpServer = server.New(cfg.BindAddr, router) + } + // Disable this here to allow main to manage graceful shutdown of the entire app. httpServer.HandleOSSignals = false