Skip to content

Commit

Permalink
Adding a new Authorizer for SAS Token Authentication (#478)
Browse files Browse the repository at this point in the history
* Adding a new Authorizer for SAS Token Authentication

This commit introduces a new Authorizer for authenticating with
Blob Storage using a SAS Token

```
$ go test -v ./autorest/ -run="TestSas"
=== RUN   TestSasNewSasAuthorizerEmptyToken
--- PASS: TestSasNewSasAuthorizerEmptyToken (0.00s)
=== RUN   TestSasNewSasAuthorizerEmptyTokenWithWhitespace
--- PASS: TestSasNewSasAuthorizerEmptyTokenWithWhitespace (0.00s)
=== RUN   TestSasNewSasAuthorizerValidToken
--- PASS: TestSasNewSasAuthorizerValidToken (0.00s)
=== RUN   TestSasAuthorizerRequest
--- PASS: TestSasAuthorizerRequest (0.00s)
    authorization_sas_test.go:76: [DEBUG] Testing Case "empty querystring without a prefix"..
    authorization_sas_test.go:76: [DEBUG] Testing Case "empty querystring with a prefix"..
    authorization_sas_test.go:76: [DEBUG] Testing Case "existing querystring without a prefix"..
    authorization_sas_test.go:76: [DEBUG] Testing Case "existing querystring with a prefix"..
PASS
ok  	github.com/Azure/go-autorest/autorest	0.011s
```

* minor clean-up
  • Loading branch information
tombuildsstuff authored and jhendrixMSFT committed Oct 23, 2019
1 parent a5c6556 commit 5f1f2ad
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 0 deletions.
67 changes: 67 additions & 0 deletions autorest/authorization_sas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package autorest

// Copyright 2017 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import (
"fmt"
"net/http"
"strings"
)

// SASTokenAuthorizer implements an authorization for SAS Token Authentication
// this can be used for interaction with Blob Storage Endpoints
type SASTokenAuthorizer struct {
sasToken string
}

// NewSASTokenAuthorizer creates a SASTokenAuthorizer using the given credentials
func NewSASTokenAuthorizer(sasToken string) (*SASTokenAuthorizer, error) {
if strings.TrimSpace(sasToken) == "" {
return nil, fmt.Errorf("sasToken cannot be empty")
}

token := sasToken
if strings.HasPrefix(sasToken, "?") {
token = strings.TrimPrefix(sasToken, "?")
}

return &SASTokenAuthorizer{
sasToken: token,
}, nil
}

// WithAuthorization returns a PrepareDecorator that adds a shared access signature token to the
// URI's query parameters. This can be used for the Blob, Queue, and File Services.
//
// See https://docs.microsoft.com/en-us/rest/api/storageservices/delegate-access-with-shared-access-signature
func (sas *SASTokenAuthorizer) WithAuthorization() PrepareDecorator {
return func(p Preparer) Preparer {
return PreparerFunc(func(r *http.Request) (*http.Request, error) {
r, err := p.Prepare(r)
if err != nil {
return r, err
}

if r.URL.RawQuery != "" {
r.URL.RawQuery = fmt.Sprintf("%s&%s", r.URL.RawQuery, sas.sasToken)
} else {
r.URL.RawQuery = sas.sasToken
}

r.RequestURI = r.URL.String()
return Prepare(r)
})
}
}
113 changes: 113 additions & 0 deletions autorest/authorization_sas_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package autorest

// Copyright 2017 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import (
"net/http"
"net/url"
"testing"
)

func TestSasNewSasAuthorizerEmptyToken(t *testing.T) {
auth, err := NewSASTokenAuthorizer("")
if err == nil {
t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer didn't return an error")
}

if auth != nil {
t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer returned an authorizer")
}
}

func TestSasNewSasAuthorizerEmptyTokenWithWhitespace(t *testing.T) {
auth, err := NewSASTokenAuthorizer(" ")
if err == nil {
t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer didn't return an error")
}

if auth != nil {
t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer returned an authorizer")
}
}

func TestSasNewSasAuthorizerValidToken(t *testing.T) {
auth, err := NewSASTokenAuthorizer("abc123")
if err != nil {
t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer returned an error")
}

if auth == nil {
t.Fatalf("azure: SASTokenAuthorizer#NewSASTokenAuthorizer didn't return an authorizer")
}
}

func TestSasAuthorizerRequest(t *testing.T) {
testData := []struct {
name string
token string
input string
expected string
}{
{
name: "empty querystring without a prefix",
token: "abc123",
input: "https://example.com/foo/bar",
expected: "https://example.com/foo/bar?abc123",
},
{
name: "empty querystring with a prefix",
token: "?abc123",
input: "https://example.com/foo/bar",
expected: "https://example.com/foo/bar?abc123",
},
{
name: "existing querystring without a prefix",
token: "abc123",
input: "https://example.com/foo/bar?hello=world",
expected: "https://example.com/foo/bar?hello=world&abc123",
},
{
name: "existing querystring with a prefix",
token: "?abc123",
input: "https://example.com/foo/bar?hello=world",
expected: "https://example.com/foo/bar?hello=world&abc123",
},
}

for _, v := range testData {
t.Logf("[DEBUG] Testing Case %q..", v.name)
auth, err := NewSASTokenAuthorizer(v.token)
if err != nil {
t.Fatalf("azure: SASTokenAuthorizer#WithAuthorization expected %q but got an error", v.expected)
}
url, _ := url.ParseRequestURI(v.input)
httpReq := &http.Request{
URL: url,
}

req, err := Prepare(httpReq, auth.WithAuthorization())
if err != nil {
t.Fatalf("azure: SASTokenAuthorizer#WithAuthorization returned an error (%v)", err)
}

if req.RequestURI != v.expected {
t.Fatalf("azure: SASTokenAuthorizer#WithAuthorization failed to set QueryString header - got %q but expected %q", req.RequestURI, v.expected)
}

if req.Header.Get(http.CanonicalHeaderKey("Authorization")) != "" {
t.Fatal("azure: SASTokenAuthorizer#WithAuthorization set an Authorization header when it shouldn't!")
}
}
}

0 comments on commit 5f1f2ad

Please sign in to comment.