Documentation
¶
Index ¶
- Variables
- func GetErrorTypeText(e ErrorType) string
- func Logf(format string, args ...interface{})
- func MakeBody(content string) io.ReadCloser
- func MakeHeader(m map[string][]string) http.Header
- func NormalizedHeaderName(key string) string
- func Now() time.Time
- func OverrideClock(timestamp int64)
- func Path(u *url.URL) string
- func ReadBody(r *http.Request) ([]byte, error)
- func ReadResponseBody(r *http.Response) ([]byte, error)
- func SilentURLParse(uri string) *url.URL
- type AuthenticationError
- type Clock
- type CompatibilityTestFixture
- type Digester
- type ErrorType
- type Identifiable
- type RealClock
- type ResponseFixture
- type ResponseSigner
- type SignableResponseWriter
- type Signer
- type TestClock
- type TestFixture
Constants ¶
This section is empty.
Variables ¶
View Source
var CompatFixtures []*CompatibilityTestFixture = []*CompatibilityTestFixture{ &CompatibilityTestFixture{ TestName: "Identify a v2 signature", Request: &http.Request{ Method: "GET", Header: MakeHeader(map[string][]string{ "X-Authorization-Timestamp": []string{"1432075982"}, "Authorization": []string{`acquia-http-hmac realm="Pipet%20service",id="efdde334-fe7b-11e4-a322-1697f925ec7b",nonce="d1954337-5319-4821-8427-115542e08d10",version="2.0",headers="",signature="MRlPr/Z1WQY2sMthcaEqETRMw4gPYXlPcTpaLWS2gcc="`}, }), Host: "example.acquiapipet.net", URL: SilentURLParse("https://example.acquiapipet.net/v1.0/task-status/133?limit=10"), }, SecretKey: "W5PeGMxSItNerkNFqQMfYiJvH14WzVJMy54CPoTAYoI=", SystemTime: 1432075982, Digest: sha256.New, Expected: "MRlPr/Z1WQY2sMthcaEqETRMw4gPYXlPcTpaLWS2gcc=", }, &CompatibilityTestFixture{ TestName: "Identify a v1 signature", Request: &http.Request{ Method: "POST", Body: MakeBody("test content"), Header: MakeHeader(map[string][]string{ "Authorization": []string{"Acquia efdde334-fe7b-11e4-a322-1697f925ec7b:6DQcBYwaKdhRm/eNBKIN2jM8HF8="}, "Content-Type": []string{"text/plain"}, "Date": []string{"Fri, 19 Mar 1982 00:00:04 GMT"}, }), URL: SilentURLParse("http://example.com/resource/1?key=value"), }, SecretKey: "secret-key", SystemTime: 1432075982, Digest: sha1.New, Expected: "6DQcBYwaKdhRm/eNBKIN2jM8HF8=", }, &CompatibilityTestFixture{ TestName: "Fail to identify an unimplemented (oauth) signature", Request: &http.Request{ Method: "POST", Body: MakeBody("test content"), Header: MakeHeader(map[string][]string{ "Authorization": []string{`OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1318622958",oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",oauth_version="1.0"`}, "Content-Type": []string{"text/plain"}, "Date": []string{"Fri, 19 Mar 1982 00:00:04 GMT"}, }), URL: SilentURLParse("http://example.com/resource/1?key=value"), }, SecretKey: "secret-key", SystemTime: 1432075982, Digest: sha1.New, Expected: "", }, }
View Source
var Fixtures []*TestFixture = []*TestFixture{ &TestFixture{ TestName: "v1 - simple GET request - invalid header in v2", SystemTime: 1432075982, Digest: sha1.New, Expected: map[string]string{ "v1": "7Tq3+JP3lAu4FoJz81XEx5+qfOc=", }, Request: &http.Request{ Method: "GET", URL: SilentURLParse("http://example.com/resource/1?key=value"), Header: MakeHeader(map[string][]string{}), }, AuthHeaders: map[string]string{ "id": "efdde334-fe7b-11e4-a322-1697f925ec7b", }, SecretKey: "secret-key", ErrorType: map[string]ErrorType{ "v2": ErrorTypeInvalidAuthHeader, }, ExpectedHeader: map[string]string{ "v1": "Acquia efdde334-fe7b-11e4-a322-1697f925ec7b:7Tq3+JP3lAu4FoJz81XEx5+qfOc=", }, }, &TestFixture{ TestName: "v1 - valid request without additional signed headers - invalid header in v2", SystemTime: 1432075982, Digest: sha1.New, Expected: map[string]string{ "v1": "6DQcBYwaKdhRm/eNBKIN2jM8HF8=", }, Request: &http.Request{ Method: "POST", Body: MakeBody("test content"), Header: MakeHeader(map[string][]string{ "Content-Type": []string{"text/plain"}, "Date": []string{"Fri, 19 Mar 1982 00:00:04 GMT"}, }), URL: SilentURLParse("http://example.com/resource/1?key=value"), }, AuthHeaders: map[string]string{ "id": "efdde334-fe7b-11e4-a322-1697f925ec7b", }, SecretKey: "secret-key", ErrorType: map[string]ErrorType{ "v2": ErrorTypeInvalidAuthHeader, }, ExpectedHeader: map[string]string{ "v1": "Acquia efdde334-fe7b-11e4-a322-1697f925ec7b:6DQcBYwaKdhRm/eNBKIN2jM8HF8=", }, }, &TestFixture{ TestName: "v1 - valid request with additional signed headers - invalid header in v2", SystemTime: 1432075982, Digest: sha1.New, Expected: map[string]string{ "v1": "QRMtvnGmlP1YbaTwpWyB/6A8dRU=", }, Request: &http.Request{ Method: "POST", Body: MakeBody("test content"), Header: MakeHeader(map[string][]string{ "Content-Type": []string{"text/plain"}, "Date": []string{"Fri, 19 Mar 1982 00:00:04 GMT"}, "Custom1": []string{"Value1"}, }), URL: SilentURLParse("http://example.com/resource/1?key=value"), }, AuthHeaders: map[string]string{ "headers": "Custom1", "id": "efdde334-fe7b-11e4-a322-1697f925ec7b", }, SecretKey: "secret-key", ErrorType: map[string]ErrorType{ "v2": ErrorTypeInvalidAuthHeader, }, ExpectedHeader: map[string]string{}, }, &TestFixture{ TestName: "v2 - valid GET request 2", SystemTime: 1432075982, Digest: sha256.New, Expected: map[string]string{ "v2": "1Ku5UroiW1knVP6GH4l7Z4IuQSRxZO2gp/e5yhapv1s=", }, Request: &http.Request{ Method: "GET", Header: MakeHeader(map[string][]string{ "X-Authorization-Timestamp": []string{"1432075982"}, }), Host: "example.acquiapipet.net", URL: SilentURLParse("https://example.acquiapipet.net/v1.0/task-status/145?limit=1"), }, AuthHeaders: map[string]string{ "realm": "Pipet service", "id": "615d6517-1cea-4aa3-b48e-96d83c16c4dd", "nonce": "24c0c836-4f6c-4ed6-a6b0-e091d75ea19d", "version": "2.0", }, SecretKey: "TXkgU2VjcmV0IEtleSBUaGF0IGlzIFZlcnkgU2VjdXJl", Response: &ResponseFixture{ Expected: map[string]string{ "v2": "C98MEJHnQSNiYCxmI4CxJegO62sGZdzEEiSXgSIoxlo=", }, Response: PrepareResponseWriter(`{"id": 145, "status": "in-progress"}`), }, ErrorType: map[string]ErrorType{}, ExpectedHeader: map[string]string{ "v2": `acquia-http-hmac id="615d6517-1cea-4aa3-b48e-96d83c16c4dd",nonce="24c0c836-4f6c-4ed6-a6b0-e091d75ea19d",realm="Pipet%20service",signature="1Ku5UroiW1knVP6GH4l7Z4IuQSRxZO2gp/e5yhapv1s=",version="2.0"`, }, }, &TestFixture{ TestName: "v2 - valid GET request 3", SystemTime: 1432075982, Digest: sha256.New, Expected: map[string]string{ "v2": "yoHiYvx79ssSDIu3+OldpbFs8RsjrMXgRoM89d5t+zA=", }, Request: &http.Request{ Method: "GET", Header: MakeHeader(map[string][]string{ "X-Authorization-Timestamp": []string{"1432075982"}, "X-Custom-Signer1": []string{"custom-1"}, "X-Custom-Signer2": []string{"custom-2"}, }), Host: "example.pipeline.io", URL: SilentURLParse("https://example.pipeline.io/api/v1/ci/pipelines"), }, AuthHeaders: map[string]string{ "realm": "CIStore", "id": "e7fe97fa-a0c8-4a42-ab8e-2c26d52df059", "nonce": "a9938d07-d9f0-480c-b007-f1e956bcd027", "headers": "X-Custom-Signer1;X-Custom-Signer2", "version": "2.0", }, SecretKey: "bXlzZWNyZXRzZWNyZXR0aGluZ3Rva2VlcA==", Response: &ResponseFixture{ Expected: map[string]string{ "v2": "cUDFSS5tN5vBBS7orIfUag8jhkaGouBb/o8fstUvTF8=", }, Response: PrepareResponseWriter(`[{"pipeline_id":"39b5d58d-0a8f-437d-8dd6-4da50dcc87b7","sitename":"enterprise-g1:sfwiptravis","name":"pipeline.yml","last_job_id":"810e4344-1bed-4fd0-a642-1ba17eb996d5","last_branch":"validate-yaml","last_requested":"2016-03-25T20:26:39.000Z","last_finished":null,"last_status":"succeeded","last_duration":null}]`), }, ErrorType: map[string]ErrorType{}, ExpectedHeader: map[string]string{ "v2": `acquia-http-hmac headers="X-Custom-Signer1%3BX-Custom-Signer2",id="e7fe97fa-a0c8-4a42-ab8e-2c26d52df059",nonce="a9938d07-d9f0-480c-b007-f1e956bcd027",realm="CIStore",signature="yoHiYvx79ssSDIu3+OldpbFs8RsjrMXgRoM89d5t+zA=",version="2.0"`, }, }, &TestFixture{ TestName: "v2 - valid GET request", SystemTime: 1432075982, Digest: sha256.New, Expected: map[string]string{ "v2": "MRlPr/Z1WQY2sMthcaEqETRMw4gPYXlPcTpaLWS2gcc=", }, Request: &http.Request{ Method: "GET", Header: MakeHeader(map[string][]string{ "X-Authorization-Timestamp": []string{"1432075982"}, }), Host: "example.acquiapipet.net", URL: SilentURLParse("https://example.acquiapipet.net/v1.0/task-status/133?limit=10"), }, AuthHeaders: map[string]string{ "realm": "Pipet service", "id": "efdde334-fe7b-11e4-a322-1697f925ec7b", "nonce": "d1954337-5319-4821-8427-115542e08d10", "version": "2.0", }, SecretKey: "W5PeGMxSItNerkNFqQMfYiJvH14WzVJMy54CPoTAYoI=", Response: &ResponseFixture{ Expected: map[string]string{ "v2": "M4wYp1MKvDpQtVOnN7LVt9L8or4pKyVLhfUFVJxHemU=", }, Response: PrepareResponseWriter(`{"id": 133, "status": "done"}`), }, ErrorType: map[string]ErrorType{}, ExpectedHeader: map[string]string{ "v2": `acquia-http-hmac id="efdde334-fe7b-11e4-a322-1697f925ec7b",nonce="d1954337-5319-4821-8427-115542e08d10",realm="Pipet%20service",signature="MRlPr/Z1WQY2sMthcaEqETRMw4gPYXlPcTpaLWS2gcc=",version="2.0"`, }, }, &TestFixture{ TestName: "v2 - valid POST request", SystemTime: 1432075982, Digest: sha256.New, Expected: map[string]string{ "v2": "XDBaXgWFCY3aAgQvXyGXMbw9Vds2WPKJe2yP+1eXQgM=", }, Request: &http.Request{ Method: "POST", Body: MakeBody("{\"method\":\"hi.bob\",\"params\":[\"5\",\"4\",\"8\"]}"), ContentLength: int64(len("{\"method\":\"hi.bob\",\"params\":[\"5\",\"4\",\"8\"]}")), Header: MakeHeader(map[string][]string{ "X-Authorization-Timestamp": []string{"1432075982"}, "X-Authorization-Content-SHA256": []string{"6paRNxUA7WawFxJpRp4cEixDjHq3jfIKX072k9slalo="}, "Content-Type": []string{"application/json"}, }), Host: "example.acquiapipet.net", URL: SilentURLParse("https://example.acquiapipet.net/v1.0/task/"), }, AuthHeaders: map[string]string{ "realm": "Pipet service", "id": "efdde334-fe7b-11e4-a322-1697f925ec7b", "nonce": "d1954337-5319-4821-8427-115542e08d10", "version": "2.0", }, SecretKey: "W5PeGMxSItNerkNFqQMfYiJvH14WzVJMy54CPoTAYoI=", Response: &ResponseFixture{ Expected: map[string]string{ "v2": "LusIUHmqt9NOALrQ4N4MtXZEFE03MjcDjziK+vVqhvQ=", }, Response: PrepareResponseWriter(``), }, ErrorType: map[string]ErrorType{}, ExpectedHeader: map[string]string{ "v2": `acquia-http-hmac id="efdde334-fe7b-11e4-a322-1697f925ec7b",nonce="d1954337-5319-4821-8427-115542e08d10",realm="Pipet%20service",signature="XDBaXgWFCY3aAgQvXyGXMbw9Vds2WPKJe2yP+1eXQgM=",version="2.0"`, }, }, &TestFixture{ TestName: "v2 - valid POST request for register endpoint", SystemTime: 1449578521, Digest: sha256.New, Expected: map[string]string{ "v2": "4VtBHjqrdDeYrJySoJVDUHpN9u3vyTsyOLz4chezi98=", }, Request: &http.Request{ Method: "POST", Body: MakeBody("{\"method\":\"hi.bob\",\"params\":[\"5\",\"4\",\"8\"]}"), ContentLength: int64(len("{\"method\":\"hi.bob\",\"params\":[\"5\",\"4\",\"8\"]}")), Header: MakeHeader(map[string][]string{ "X-Authorization-Timestamp": []string{"1449578521"}, "X-Authorization-Content-SHA256": []string{"6paRNxUA7WawFxJpRp4cEixDjHq3jfIKX072k9slalo="}, "Content-Type": []string{"application/json"}, }), Host: "54.154.147.142:3000", URL: SilentURLParse("http://54.154.147.142:3000/register"), }, AuthHeaders: map[string]string{ "realm": "Plexus", "id": "f0d16792-cdc9-4585-a5fd-bae3d898d8c5", "nonce": "64d02132-40bf-4fce-85bf-3f1bb1bfe7dd", "version": "2.0", }, SecretKey: "eox4TsBBPhpi737yMxpdBbr3sgg/DEC4m47VXO0B8qJLsbdMsmN47j/ZF/EFpyUKtAhm0OWXMGaAjRaho7/93Q==", ErrorType: map[string]ErrorType{}, ExpectedHeader: map[string]string{ "v2": `acquia-http-hmac id="f0d16792-cdc9-4585-a5fd-bae3d898d8c5",nonce="64d02132-40bf-4fce-85bf-3f1bb1bfe7dd",realm="Plexus",signature="4VtBHjqrdDeYrJySoJVDUHpN9u3vyTsyOLz4chezi98=",version="2.0"`, }, }, &TestFixture{ TestName: "v2 - valid POST request with signed headers", SystemTime: 1449578521, Digest: sha256.New, Expected: map[string]string{ "v2": "0duvqeMauat7pTULg3EgcSmBjrorrcRkGKxRDtZEa1c=", }, Request: &http.Request{ Method: "POST", Body: MakeBody(`{"cloud_endpoint":"https://cloudapi.acquia.com/v1","cloud_user":"example@acquia.com","cloud_pass":"password","branch":"validate"}`), ContentLength: int64(len(`{"cloud_endpoint":"https://cloudapi.acquia.com/v1","cloud_user":"example@acquia.com","cloud_pass":"password","branch":"validate"}`)), Header: MakeHeader(map[string][]string{ "X-Authorization-Timestamp": []string{"1449578521"}, "X-Authorization-Content-SHA256": []string{"2YGTI4rcSnOEfd7hRwJzQ2OuJYqAf7jzyIdcBXCGreQ="}, "Content-Type": []string{"application/json"}, "X-Custom-Signer1": []string{"custom-1"}, "X-Custom-Signer2": []string{"custom-2"}, }), Host: "example.pipeline.io", URL: SilentURLParse("https://example.pipeline.io/api/v1/ci/pipelines/39b5d58d-0a8f-437d-8dd6-4da50dcc87b7/start"), }, AuthHeaders: map[string]string{ "realm": "CIStore", "id": "e7fe97fa-a0c8-4a42-ab8e-2c26d52df059", "nonce": "a9938d07-d9f0-480c-b007-f1e956bcd027", "headers": "X-Custom-Signer1;X-Custom-Signer2", "version": "2.0", }, SecretKey: "bXlzZWNyZXRzZWNyZXR0aGluZ3Rva2VlcA==", Response: &ResponseFixture{ Expected: map[string]string{ "v2": "SlOYi3pUZADkzU9wEv7kw3hmxjlEyMqBONFEVd7iDbM=", }, Response: PrepareResponseWriter(`"57674bb1-f2ce-4d0f-bfdc-736a78aa027a"`), }, ErrorType: map[string]ErrorType{}, ExpectedHeader: map[string]string{ "v2": `acquia-http-hmac headers="X-Custom-Signer1%3BX-Custom-Signer2",id="e7fe97fa-a0c8-4a42-ab8e-2c26d52df059",nonce="a9938d07-d9f0-480c-b007-f1e956bcd027",realm="CIStore",signature="0duvqeMauat7pTULg3EgcSmBjrorrcRkGKxRDtZEa1c=",version="2.0"`, }, }, &TestFixture{ TestName: "v2 - request with missing timestamp", SystemTime: 1432075982, Digest: sha256.New, Expected: map[string]string{}, Request: &http.Request{ Method: "POST", Body: MakeBody("{\"method\":\"hi.bob\",\"params\":[\"5\",\"4\",\"8\"]}"), Header: MakeHeader(map[string][]string{ "X-Authorization-Content-SHA256": []string{"6paRNxUA7WawFxJpRp4cEixDjHq3jfIKX072k9slalo="}, "Content-Type": []string{"application/json"}, }), Host: "example.acquiapipet.net", URL: SilentURLParse("https://example.acquiapipet.net/v1.0/task/"), }, AuthHeaders: map[string]string{ "realm": "Pipet service", "id": "efdde334-fe7b-11e4-a322-1697f925ec7b", "nonce": "d1954337-5319-4821-8427-115542e08d10", "version": "2.0", }, SecretKey: "W5PeGMxSItNerkNFqQMfYiJvH14WzVJMy54CPoTAYoI=", ErrorType: map[string]ErrorType{ "v2": ErrorTypeMissingRequiredHeader, }, ExpectedHeader: map[string]string{}, }, &TestFixture{ TestName: "v2 - outdated keypair (non-b64 encoded secret key)", SystemTime: 1432075982, Digest: sha256.New, Expected: map[string]string{}, Request: &http.Request{ Method: "POST", Body: MakeBody("{\"method\":\"hi.bob\",\"params\":[\"5\",\"4\",\"8\"]}"), Header: MakeHeader(map[string][]string{ "X-Authorization-Timestamp": []string{"1432075982"}, "X-Authorization-Content-SHA256": []string{"6paRNxUA7WawFxJpRp4cEixDjHq3jfIKX072k9slalo="}, "Content-Type": []string{"application/json"}, }), Host: "example.acquiapipet.net", URL: SilentURLParse("https://example.acquiapipet.net/v1.0/task/"), }, AuthHeaders: map[string]string{ "realm": "Pipet service", "id": "efdde334-fe7b-11e4-a322-1697f925ec7b", "nonce": "d1954337-5319-4821-8427-115542e08d10", "version": "2.0", }, SecretKey: "this is a useless secret key for v2 authentication", ErrorType: map[string]ErrorType{ "v2": ErrorTypeOutdatedKeypair, }, ExpectedHeader: map[string]string{}, }, }
View Source
var Log *log.Logger
Functions ¶
func GetErrorTypeText ¶
func MakeBody ¶
func MakeBody(content string) io.ReadCloser
func NormalizedHeaderName ¶
func OverrideClock ¶
func OverrideClock(timestamp int64)
func SilentURLParse ¶
Types ¶
type AuthenticationError ¶
AuthenticationError no longer implements the error interface because: - go is dumb - the people behind go are illogical bastards - and i'm also dumb for repeatedly falling into the trap the two aforementioned things lay me No, it won't ever implement error. Not until there is a unified way to check for nils in Go. Trust me. It's better this way. One day you'll thank me for it. You'll never know what it's like to fall into the trap set by this four times and waste 32 perfectly fine manhours on it.
type Identifiable ¶
type ResponseFixture ¶
type ResponseFixture struct { Expected map[string]string Response *SignableResponseWriter }
type ResponseSigner ¶
type ResponseSigner interface { SignResponse(req *http.Request, rw *SignableResponseWriter, secret string) (string, *AuthenticationError) SignResponseDirect(req *http.Request, rw *SignableResponseWriter, secret string) *AuthenticationError Check(req *http.Request, resp *http.Response, secret string) *AuthenticationError SetTrailer(rw http.ResponseWriter) }
type SignableResponseWriter ¶
type SignableResponseWriter struct { http.ResponseWriter Body bytes.Buffer // contains filtered or unexported fields }
func NewDummySignableResponseWriter ¶
func NewDummySignableResponseWriter(body []byte) *SignableResponseWriter
func NewSignableResponseWriter ¶
func NewSignableResponseWriter(h http.ResponseWriter) *SignableResponseWriter
func PrepareResponseWriter ¶
func PrepareResponseWriter(b string) *SignableResponseWriter
func (*SignableResponseWriter) Close ¶
func (s *SignableResponseWriter) Close() (int, error)
func (*SignableResponseWriter) Header ¶
func (s *SignableResponseWriter) Header() http.Header
func (*SignableResponseWriter) Write ¶
func (s *SignableResponseWriter) Write(b []byte) (int, error)
func (*SignableResponseWriter) WriteHeader ¶
func (s *SignableResponseWriter) WriteHeader(status int)
type Signer ¶
type Signer interface { // Generates a signature according to a request. Does not alter the request. // Fails if headers necessary for signing are missing. // Does not alter the contents of the request, but req.Body's address may change. Sign(req *http.Request, authHeaders map[string]string, secret string) (string, *AuthenticationError) // Returns a regular expression that matches the authorization header for the version of the signature // that the signer represents, but not any other version. GetIdentificationRegex() *regexp.Regexp // Hashes the body of a request according to the signature specifications. // Does not alter the contents of the request, but req.Body's address may change. HashBody(req *http.Request) (string, *AuthenticationError) // Returns a response signer for this type of signature if one exists. May return nil if no specification // exists for response signing. GetResponseSigner() ResponseSigner // Reads the authorization headers from the Authorization field of a request and returns them as a map. // Does not alter the request. ParseAuthHeaders(req *http.Request) map[string]string // Verifies whether or not a request bears a valid Authorization header. // May also check other required headers and a return an error if they are missing. // In short, Check() verifies whether a signed request is valid, up to specifications and bears the expected signature. Check(req *http.Request, secret string) *AuthenticationError // Directly signs the request, generating the appropriate headers if necessary. SignDirect(req *http.Request, authHeaders map[string]string, secret string) *AuthenticationError // Generates the Authorization header's value for a request using the authorization headers and a signature. // Does not alter the request. // Does not alter the contents of the request, but req.Body's address may change. GenerateAuthorization(req *http.Request, authHeaders map[string]string, signature string) (string, *AuthenticationError) // Returns a version number, or 0 if unknown. Version() int }
type TestClock ¶
func NewTestClock ¶
Click to show internal directories.
Click to hide internal directories.