aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2018-07-10 15:29:52 +0200
committerStefan Majewsky <majewsky@gmx.net>2018-07-10 15:29:52 +0200
commit2d3eb72207f62b7bc4e08b3dc750f7da8cacb53b (patch)
tree68c7e11d476de9e8e0d16b1da9f3482e47560107
parent0eab6bf51e2fe22de6c877e01149b49bba283792 (diff)
downloadgo-schwift-2d3eb72207f62b7bc4e08b3dc750f7da8cacb53b.tar.gz
add Object.TempURL()
-rw-r--r--object.go50
-rw-r--r--object_test.go58
2 files changed, 108 insertions, 0 deletions
diff --git a/object.go b/object.go
index e2cea84..960a3a6 100644
--- a/object.go
+++ b/object.go
@@ -20,7 +20,9 @@ package schwift
import (
"bytes"
+ "crypto/hmac"
"crypto/md5"
+ "crypto/sha1"
"encoding/hex"
"fmt"
"hash"
@@ -28,6 +30,7 @@ import (
"net/http"
"net/url"
"strings"
+ "time"
)
//Object represents a Swift object. Instances are usually obtained by
@@ -590,3 +593,50 @@ func (o *Object) URL() (string, error) {
ObjectName: o.name,
}.URL(o.c.a.backend, nil)
}
+
+//TempURL is like Object.URL, but includes a token with a limited lifetime (as
+//specified by the `expires` argument) that permits anonymous access to this
+//object using the given HTTP method. This works only when the tempurl
+//middleware is set up on the server, and if the given `key` matches one of the
+//tempurl keys for this object's container or account.
+//
+//For example, if the ReadACL both on the account and container do not permit
+//anonymous read access (which is the default behavior):
+//
+// var o *schwift.Object
+// ...
+// resp, err := http.Get(o.URL())
+// //After this, resp.StatusCode == 401 (Unauthorized)
+// //because anonymous access is forbidden.
+//
+// //But if the container or account has a tempurl key...
+// key := "supersecretkey"
+// hdr := NewContainerHeaders()
+// hdr.TempURLKey().Set(key)
+// c := o.Container()
+// err := c.Update(hdr, nil)
+//
+// //...we can use it to generate temporary URLs.
+// url := o.TempURL(key, "GET", time.Now().Add(10 * time.Minute))
+// resp, err := http.Get(url)
+// //This time, resp.StatusCode == 200 because the URL includes a token.
+//
+func (o *Object) TempURL(key, method string, expires time.Time) (string, error) {
+ urlStr, err := o.URL()
+ if err != nil {
+ return "", err
+ }
+ u, err := url.Parse(urlStr)
+ if err != nil {
+ return "", err
+ }
+
+ payload := fmt.Sprintf("%s\n%d\n%s", method, expires.Unix(), u.Path)
+ mac := hmac.New(sha1.New, []byte(key))
+ mac.Write([]byte(payload))
+ signature := hex.EncodeToString(mac.Sum(nil))
+
+ u.RawQuery = fmt.Sprintf("temp_url_sig=%s&temp_url_expires=%d",
+ signature, expires.Unix())
+ return u.String(), nil
+}
diff --git a/object_test.go b/object_test.go
new file mode 100644
index 0000000..50bf622
--- /dev/null
+++ b/object_test.go
@@ -0,0 +1,58 @@
+/******************************************************************************
+*
+* Copyright 2018 Stefan Majewsky <majewsky@gmx.net>
+*
+* 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.
+*
+******************************************************************************/
+
+package schwift
+
+import (
+ "net/http"
+ "testing"
+ "time"
+)
+
+type tempurlBogusBackend struct{}
+
+func (tempurlBogusBackend) EndpointURL() string {
+ return "https://example.com/v1/AUTH_example/"
+}
+func (tempurlBogusBackend) Clone(newEndpointURL string) Backend {
+ panic("unimplemented")
+}
+func (tempurlBogusBackend) Do(req *http.Request) (*http.Response, error) {
+ panic("unimplemented")
+}
+
+func TestObjectTempURL(t *testing.T) {
+ //setup a bogus backend, account, container and object with exact names to
+ //reproducibly generate a temp URL
+ account, err := InitializeAccount(tempurlBogusBackend{})
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+
+ actualURL, err := account.Container("foo").Object("bar").TempURL("supersecretkey", "GET", time.Unix(1e9, 0))
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+
+ expectedURL := "https://example.com/v1/AUTH_example/foo/bar?temp_url_sig=ed44d92005345aee463c884d76d4850ef6d2778d&temp_url_expires=1000000000"
+ if actualURL != expectedURL {
+ t.Error("temp URL generation failed")
+ t.Logf("expected: %s\n", expectedURL)
+ t.Logf("actual: %s\n", actualURL)
+ }
+}