Merge lp:~vds/usso/better_interface into lp:usso

Proposed by Vincenzo Di Somma
Status: Merged
Merged at revision: 20
Proposed branch: lp:~vds/usso/better_interface
Merge into: lp:usso
Prerequisite: lp:~vds/usso/change_license
Diff against target: 588 lines (+408/-52)
7 files modified
example/usso_example.go (+41/-7)
oauth.go (+126/-0)
oauth_test.go (+52/-0)
url.go (+45/-0)
url_test.go (+88/-0)
usso.go (+48/-25)
usso_test.go (+8/-20)
To merge this branch: bzr merge lp:~vds/usso/better_interface
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+145174@code.launchpad.net

This proposal supersedes a proposal from 2013-01-25.

Description of the change

Refactoring the interface and handling errors a little better. There are no unit tests for the main interface, but the example shows that they work as expected. Tests will be added in a following branch, if we decide to spend more time on this project.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :
Download full text (4.8 KiB)

Hi Vincenzo,

Thanks for this branch. There are quite a few changes that need to be
made, but they're mostly cosmetic - don't be alarmed at the size of the
review :)

I've grouped things together under common headers (so [6] refers to all
the functions without documentation, for example). That makes it a bit
non-linear, but I think it's clearer that way.

I'm marking this needs-fixing because the review is so large - just to
make it disappear from the review queue, really.

[1]

19 +var email, password, tokenName, signature_method string

We should either be using camelCase or underscore_separated names, not
both, I think. It's up to you which.

[2]

56 + // This would be the easiest way to get the account data.
57 + //accounts, _ := server.GetAccounts(ssodata)
58 + //fmt.Printf("Got accounts info: %s\n", accounts)

I'm not overly fond of leaving commented-out lines in code just for
documentation purposes. We should add this to a README rather than
leaving it here.

[3]

74 + response, err := client.Do(request)
75 + if err != nil {
76 + fmt.Printf("Error: %s\n", err)
77 + }
78 + body, err := ioutil.ReadAll(response.Body)
79 + if err != nil {
80 + fmt.Println(err)
81 + }

I may be reading this wrong (not being a proper Gopher yet and all) but
I _think_ that if client.Do(request) returns an error we should probably
exit early rather than try to do something with the response, right?
Unless the response body has something useful in it, which we can't
guarantee.

[4]

82 + var b bytes.Buffer
83 + b.Write(body)
84 + fmt.Printf("response: %+v\n", b.String())

Is there any reason to print out the body here, other than to see what's
happened?

[5]

106 +func init() {
107 + // Initialize the random generator.
108 + rand.Seed(time.Now().UTC().UnixNano())
109 +}

111 +func timestamp() string {
112 + // Create a timestamp used in authorization header.
113 + return strconv.Itoa(int(time.Now().Unix()))
114 +}

116 +func nonce() string {
117 + // Create a nonce used in authorization header.
118 + return strconv.Itoa(rand.Intn(100000000))
119 +}

136 +func (oauth *SSOData) signature() (string, error) {
137 + // Depending on the signature method, create the signature from the
138 + // consumer secret, the token secret and, if required, the URL.
139 + // Supported signature methods are PLAINTEXT and HMAC-SHA1.

457 +func (server UbuntuSSOServer) GetToken(
458 + // Giving user credentials and token name, retrieves oauth credentials
459 + // for the users, the oauth credentials can be used later to sign requests.
460 + email string, password string, tokenName string) (*SSOData, error) {

481 +func (server UbuntuSSOServer) GetAccounts(ssodata *SSOData) (string, error) {

508 +func SignRequest(ssodata *SSOData, request *http.Request) error {
509 + // Given oauth credentials and a request, return it signed.

513 +func GetAuthorizationHeader(ssodata *SSOData) (string, error) {
514 + // Given oauth credentials return a valid http authorization header.

These comments should go before the function declaration (I don't know
if godoc does The Right Thing with comments after the declaration, but
most other places do it before.)

[6]

181 +func (oauth *SSOData) GetAut...

Read more...

review: Needs Fixing (code)
Revision history for this message
Vincenzo Di Somma (vds) wrote :

> [1]

Right, I'll fix that.

> [2]
> [3]
> [4]

All this comments are about example/usso_example.go, that's just an example of how to use the library, that's why there's some commented code and the error checking is not very accurate, I can still fix them if you like.

> [5]
> [6]

Will do.

Revision history for this message
Graham Binns (gmb) wrote :

> > [1]
>
> Right, I'll fix that.
>
> > [2]
> > [3]
> > [4]
>
> All this comments are about example/usso_example.go, that's just an example of
> how to use the library, that's why there's some commented code and the error
> checking is not very accurate, I can still fix them if you like.

Ah, I see. Sorry :). That's fine then :).

review: Approve (code)
lp:~vds/usso/better_interface updated
18. By Vincenzo Di Somma

Fixing comments and little code style.

19. By Vincenzo Di Somma

Commented tests.

Revision history for this message
Graham Binns (gmb) wrote :

Hi Vincenzo,

Thanks for addressing my concerns. I still think the documentation on the tests needs tweaking. The documentation you've added does describe the expected behaviour for which we're testing, but it does it in a rather opaque way. I've pastebinned a diff of how I think the documentation should read: https://pastebin.canonical.com/83278/

Generally, comments on tests should be written as something like:

// Foo() returns an instance of Bar when its returnBar parameter is set to true.
func (suite *FooTestSuite) TestFooReturnsBarWhenReturnBarParameterIsTrue (c *gocheck.C) {
    ...
}

review: Approve (code)
Revision history for this message
Vincenzo Di Somma (vds) wrote :

Ok, you are right, I'll fix it.

lp:~vds/usso/better_interface updated
20. By Vincenzo Di Somma

Commented tests in a more reasonable way thanks to gmb.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'example/usso_example.go'
2--- example/usso_example.go 2013-01-21 16:57:31 +0000
3+++ example/usso_example.go 2013-01-29 14:29:23 +0000
4@@ -1,23 +1,27 @@
5 package main
6
7 import (
8+ "bytes"
9 "encoding/json"
10 "fmt"
11+ "io/ioutil"
12 "launchpad.net/usso"
13+ "net/http"
14 )
15
16-var email string
17-var password string
18-var tokenName string
19+var email, password, tokenName, signature_method string
20
21 func inputParams() {
22- fmt.Println("This application will query the staging Ubuntu SSO Server to fetch authorisation tokens.")
23+ fmt.Println("This application will query the staging Ubuntu SSO Server" +
24+ " to fetch authorisation tokens.")
25 fmt.Print("Enter email: ")
26 fmt.Scanf("%s", &email)
27 fmt.Print("Enter password: ")
28 fmt.Scanf("%s", &password)
29 fmt.Print("Enter token name: ")
30 fmt.Scanf("%s", &tokenName)
31+ fmt.Print("Enter signature method (PLAINTEXT or HMAC-SHA1): ")
32+ fmt.Scanf("%s", &signature_method)
33 }
34
35 func main() {
36@@ -26,16 +30,46 @@
37 // Fetch the tokens using usso.GetToken.
38 fmt.Println("Fetching tokens from staging server...")
39 server := usso.StagingUbuntuSSOServer
40- // One would use server := usso.ProductionUbuntuSSOServer to use the production Ubuntu SSO Server.
41- token, err := server.GetToken(email, password, tokenName)
42+ // One would use server := usso.ProductionUbuntuSSOServer
43+ // to use the production Ubuntu SSO Server.
44+ ssodata, err := server.GetToken(email, password, tokenName)
45 if err != nil {
46 panic(err)
47 }
48 // Format the result as json for displaying it:
49- json_token, err := json.Marshal(token)
50+ json_token, err := json.Marshal(ssodata)
51 if err != nil {
52 panic(err)
53 }
54 fmt.Printf("Got tokens: %s\n", json_token)
55
56+ // This would be the easiest way to get the account data.
57+ //accounts, _ := server.GetAccounts(ssodata)
58+ //fmt.Printf("Got accounts info: %s\n", accounts)
59+
60+ // But this shows how to sign a generic request.
61+ ssodata.BaseURL = fmt.Sprintf(
62+ "https://login.staging.ubuntu.com/api/v2/accounts/%s",
63+ ssodata.ConsumerKey)
64+ ssodata.HTTPMethod = "GET"
65+ ssodata.SignatureMethod = signature_method
66+ request, _ := http.NewRequest(ssodata.HTTPMethod, ssodata.BaseURL, nil)
67+ usso.SignRequest(ssodata, request)
68+
69+ if err != nil {
70+ fmt.Printf("Error: %s\n", err)
71+ }
72+ // run the request
73+ client := &http.Client{}
74+ response, err := client.Do(request)
75+ if err != nil {
76+ fmt.Printf("Error: %s\n", err)
77+ }
78+ body, err := ioutil.ReadAll(response.Body)
79+ if err != nil {
80+ fmt.Println(err)
81+ }
82+ var b bytes.Buffer
83+ b.Write(body)
84+ fmt.Printf("response: %+v\n", b.String())
85 }
86
87=== added file 'oauth.go'
88--- oauth.go 1970-01-01 00:00:00 +0000
89+++ oauth.go 2013-01-29 14:29:23 +0000
90@@ -0,0 +1,126 @@
91+package usso
92+
93+import (
94+ "crypto/hmac"
95+ "crypto/sha1"
96+ "encoding/base64"
97+ "errors"
98+ "fmt"
99+ "math/rand"
100+ "net/http"
101+ "net/url"
102+ "strconv"
103+ "time"
104+)
105+
106+// Initialize the random generator.
107+func init() {
108+ rand.Seed(time.Now().UTC().UnixNano())
109+}
110+
111+// Create a timestamp used in authorization header.
112+func timestamp() string {
113+ return strconv.Itoa(int(time.Now().Unix()))
114+}
115+
116+// Create a nonce used in authorization header.
117+func nonce() string {
118+ return strconv.Itoa(rand.Intn(100000000))
119+}
120+
121+// Contains the oauth data to perform a request.
122+type SSOData struct {
123+ HTTPMethod string `json:"-"`
124+ BaseURL string `json:"-"`
125+ Params url.Values `json:"-"`
126+ Nonce string `json:"-"`
127+ Timestamp string `json:"-"`
128+ SignatureMethod string `json:"-"`
129+ ConsumerKey string `json:"consumer_key"`
130+ ConsumerSecret string `json:"consumer_secret"`
131+ TokenKey string `json:"token_key"`
132+ TokenName string `json:"token_name"`
133+ TokenSecret string `json:"token_secret"`
134+}
135+
136+// Depending on the signature method, create the signature from the
137+// consumer secret, the token secret and, if required, the URL.
138+// Supported signature methods are PLAINTEXT and HMAC-SHA1.
139+func (oauth *SSOData) signature() (string, error) {
140+ switch oauth.SignatureMethod {
141+ case "PLAINTEXT":
142+ return fmt.Sprintf(
143+ `%s%%26%s`,
144+ oauth.ConsumerSecret,
145+ oauth.TokenSecret), nil
146+ case "HMAC-SHA1":
147+ base_url, err := NormalizeURL(oauth.BaseURL)
148+ if err != nil {
149+ return "", err
150+ }
151+ params, err := NormalizeParameters(oauth.Params)
152+ if err != nil {
153+ return "", err
154+ }
155+ base_string := fmt.Sprintf(`%s&%s&%s%s%s%s%s%s%s`,
156+ oauth.HTTPMethod,
157+ url.QueryEscape(base_url),
158+ url.QueryEscape(params),
159+ url.QueryEscape("oauth_consumer_key="+oauth.ConsumerKey),
160+ url.QueryEscape("&oauth_nonce="+oauth.Nonce),
161+ url.QueryEscape("&oauth_signature_method="+oauth.SignatureMethod),
162+ url.QueryEscape("&oauth_timestamp="+oauth.Timestamp),
163+ url.QueryEscape("&oauth_token="+oauth.TokenKey),
164+ url.QueryEscape("&oauth_version=1.0"))
165+ hashfun := hmac.New(sha1.New, []byte(
166+ oauth.ConsumerSecret+"&"+oauth.TokenSecret))
167+ hashfun.Write([]byte(base_string))
168+ rawsignature := hashfun.Sum(nil)
169+ base64signature := make(
170+ []byte, base64.StdEncoding.EncodedLen(len(rawsignature)))
171+ base64.StdEncoding.Encode(base64signature, rawsignature)
172+ return string(base64signature), nil
173+ default:
174+ return "", errors.New(
175+ "usso/oauth: Oauth Signature Method not supported.")
176+ }
177+ return "", nil
178+}
179+
180+// Sign the provided request.
181+func (oauth *SSOData) GetAuthorizationHeader() (string, error) {
182+ if oauth.Nonce == "" {
183+ oauth.Nonce = nonce()
184+ }
185+ if oauth.Timestamp == "" {
186+ oauth.Timestamp = timestamp()
187+ }
188+ signature, err := oauth.signature()
189+ if err != nil {
190+ return "", err
191+ }
192+ auth := fmt.Sprintf(
193+ `OAuth realm="API", `+
194+ `oauth_consumer_key="%s", `+
195+ `oauth_token="%s", `+
196+ `oauth_signature_method="%s", `+
197+ `oauth_signature="%s", `+
198+ `oauth_timestamp="%s", `+
199+ `oauth_nonce="%s", `+
200+ `oauth_version="1.0"`,
201+ url.QueryEscape(oauth.ConsumerKey),
202+ url.QueryEscape(oauth.TokenKey),
203+ oauth.SignatureMethod,
204+ signature,
205+ url.QueryEscape(oauth.Timestamp),
206+ url.QueryEscape(oauth.Nonce))
207+
208+ return auth, nil
209+}
210+
211+// Sign the provided request.
212+func (oauth *SSOData) SignRequest(req *http.Request) error {
213+ auth, error := oauth.GetAuthorizationHeader()
214+ req.Header.Add("Authorization", auth)
215+ return error
216+}
217
218=== added file 'oauth_test.go'
219--- oauth_test.go 1970-01-01 00:00:00 +0000
220+++ oauth_test.go 2013-01-29 14:29:23 +0000
221@@ -0,0 +1,52 @@
222+package usso
223+
224+import (
225+ . "launchpad.net/gocheck"
226+ "net/http"
227+ "net/url"
228+)
229+
230+func (suite *USSOTestSuite) TestSignRequestPlainText(c *C) {
231+ baseUrl := "https://localhost"
232+ ssodata := SSOData{BaseURL: baseUrl, ConsumerKey: consumerKey,
233+ ConsumerSecret: consumerSecret, TokenKey: tokenKey,
234+ TokenName: tokenName, TokenSecret: tokenSecret}
235+ request, _ := http.NewRequest("GET", baseUrl, nil)
236+ ssodata.HTTPMethod = "GET"
237+ ssodata.SignatureMethod = "PLAINTEXT"
238+ err := ssodata.SignRequest(request)
239+ c.Assert(err, IsNil)
240+ authHeader := request.Header["Authorization"][0]
241+ c.Assert(authHeader, Matches, `^OAuth.*`)
242+ c.Assert(authHeader, Matches, `.*realm="API".*`)
243+ c.Assert(authHeader, Matches,
244+ `.*oauth_consumer_key="`+url.QueryEscape(ssodata.ConsumerKey)+`".*`)
245+ c.Assert(authHeader, Matches,
246+ `.*oauth_token="`+url.QueryEscape(ssodata.TokenKey)+`".*`)
247+ c.Assert(authHeader, Matches,
248+ `.*oauth_signature="`+url.QueryEscape(
249+ ssodata.ConsumerSecret+`&`+ssodata.TokenSecret)+`.*`)
250+}
251+
252+// Test the request signing with oauth_signature_method = SHA1
253+func (suite *USSOTestSuite) TestSignRequestSHA1(c *C) {
254+ baseUrl := "https://localhost"
255+ ssodata := SSOData{BaseURL: baseUrl, ConsumerKey: consumerKey,
256+ ConsumerSecret: consumerSecret, TokenKey: tokenKey,
257+ TokenName: tokenName, TokenSecret: tokenSecret,
258+ Nonce: "10888885", Timestamp: "1358853126"}
259+ request, _ := http.NewRequest("GET", baseUrl, nil)
260+ ssodata.HTTPMethod = "GET"
261+ ssodata.SignatureMethod = "HMAC-SHA1"
262+ err := ssodata.SignRequest(request)
263+ c.Assert(err, IsNil)
264+ authHeader := request.Header["Authorization"][0]
265+ c.Assert(authHeader, Matches, `^OAuth.*`)
266+ c.Assert(authHeader, Matches, `.*realm="API".*`)
267+ c.Assert(authHeader, Matches,
268+ `.*oauth_consumer_key="`+url.QueryEscape(ssodata.ConsumerKey)+`".*`)
269+ c.Assert(authHeader, Matches,
270+ `.*oauth_token="`+url.QueryEscape(ssodata.TokenKey)+`".*`)
271+ c.Assert(authHeader, Matches,
272+ `.*oauth_signature="`+"amJnYeek4G9ObTgTiE2y6cwTyPg="+`.*`)
273+}
274
275=== added file 'url.go'
276--- url.go 1970-01-01 00:00:00 +0000
277+++ url.go 2013-01-29 14:29:23 +0000
278@@ -0,0 +1,45 @@
279+package usso
280+
281+import (
282+ "fmt"
283+ "net/url"
284+ "strings"
285+)
286+
287+// Remove the standard ports from the URL.
288+func normalizeHost(scheme, host_spec string) string {
289+ standard_ports := map[string]string{
290+ "http": "80",
291+ "https": "443",
292+ }
293+ host_parts := strings.Split(host_spec, ":")
294+ if len(host_parts) == 2 && host_parts[1] == standard_ports[scheme] {
295+ // There's a port, but it's the default one. Leave it out.
296+ return host_parts[0]
297+ }
298+ return host_spec
299+}
300+
301+// Normalize the URL according to OAuth specs.
302+func NormalizeURL(input_url string) (string, error) {
303+ parsed_url, err := url.Parse(input_url)
304+ if err != nil {
305+ return "", err
306+ }
307+
308+ host := normalizeHost(parsed_url.Scheme, parsed_url.Host)
309+ normalized_url := fmt.Sprintf(
310+ "%v://%v%v", parsed_url.Scheme, host, parsed_url.Path)
311+ return normalized_url, nil
312+}
313+
314+// Normalize the parameters in the query string according to OAuth specs.
315+func NormalizeParameters(parameters url.Values) (string, error) {
316+ filtered_map := make(url.Values, len(parameters))
317+ for param, value := range parameters {
318+ if param != "oauth_signature" {
319+ filtered_map[param] = value
320+ }
321+ }
322+ return filtered_map.Encode(), nil
323+}
324
325=== added file 'url_test.go'
326--- url_test.go 1970-01-01 00:00:00 +0000
327+++ url_test.go 2013-01-29 14:29:23 +0000
328@@ -0,0 +1,88 @@
329+package usso
330+
331+import (
332+ "launchpad.net/gocheck"
333+ "net/url"
334+)
335+
336+// When NormalizeURL() is passed a simple URL, it will make no changes
337+// to it.
338+func (suite *USSOTestSuite) TestNormalizeURLReturnsBasicURL(c *gocheck.C) {
339+ output, err := NormalizeURL("http://example.com/path")
340+ c.Check(err, gocheck.Equals, nil)
341+ c.Check(output, gocheck.Equals, "http://example.com/path")
342+}
343+
344+// NormalizeURL() strips the ":80" from http:// URLs that contain it.
345+func (suite *USSOTestSuite) TestNormalizeURLStripsStandardHTTPPort(
346+ c *gocheck.C) {
347+ output, err := NormalizeURL("http://example.com:80/path")
348+ c.Check(err, gocheck.Equals, nil)
349+ c.Check(output, gocheck.Equals, "http://example.com/path")
350+}
351+
352+// NormalizeURL() strips the ":443" from https:// URLs that contain it.
353+func (suite *USSOTestSuite) TestNormalizeURLStripsStandardHTTPSPort(
354+ c *gocheck.C) {
355+ output, err := NormalizeURL("https://example.com:443/path")
356+ c.Check(err, gocheck.Equals, nil)
357+ c.Check(output, gocheck.Equals, "https://example.com/path")
358+}
359+
360+// NormalizeURL() does not remove non-standard ports from the URL.
361+func (suite *USSOTestSuite) TestNormalizeURLLeavesNonstandardPort(
362+ c *gocheck.C) {
363+ output, err := NormalizeURL("http://example.com:8080/")
364+ c.Check(err, gocheck.Equals, nil)
365+ c.Check(output, gocheck.Equals, "http://example.com:8080/")
366+}
367+
368+// NormalizeURL() strips the query string from URLs.
369+func (suite *USSOTestSuite) TestNormalizeURLStripsParameters(c *gocheck.C) {
370+ output, err := NormalizeURL("http://example.com/path?query=value&param=arg")
371+ c.Check(err, gocheck.Equals, nil)
372+ c.Check(output, gocheck.Equals, "http://example.com/path")
373+}
374+
375+// NormalizeParameters() takes a url.Values instance and returns an
376+// encoded key=value string containing the parameters in that instance.
377+func (suite *USSOTestSuite) TestNormalizeParametersReturnsParameters(
378+ c *gocheck.C) {
379+ output, err := NormalizeParameters(url.Values{"param": []string{"value"}})
380+ c.Check(err, gocheck.Equals, nil)
381+ c.Check(output, gocheck.Equals, "param=value")
382+}
383+
384+// NormalizeParameters() encodes multiple key/value parameters as a
385+// query string.
386+func (suite *USSOTestSuite) TestNormalizeParametersConcatenatesParameters(
387+ c *gocheck.C) {
388+ output, err := NormalizeParameters(
389+ url.Values{"a": []string{"1"}, "b": []string{"2"}})
390+ c.Check(err, gocheck.Equals, nil)
391+ c.Check(output, gocheck.Matches, "(a=1&b=2|b=2&a=1)")
392+}
393+
394+// NormalizeParameters() escapes the parameters correctly when encoding
395+// them as a query string.
396+func (suite *USSOTestSuite) TestNormalizeParametersEscapesParameters(
397+ c *gocheck.C) {
398+ output, err := NormalizeParameters(url.Values{"a&b": []string{"1"}})
399+ c.Check(err, gocheck.Equals, nil)
400+ c.Check(output, gocheck.Equals, "a%26b=1")
401+}
402+
403+// If oauth_signature appears in the parameters passed to
404+// NormalizeParameters(), it is omitted in the returned string as it does not
405+// have to be included in the computation of the new oauth_signature.
406+func (suite *USSOTestSuite) TestNormalizeParametersOmitsOAuthSignature(
407+ c *gocheck.C) {
408+ params := url.Values{
409+ "a": []string{"1"},
410+ "oauth_signature": []string{"foobarsplatszot"},
411+ "z": []string{"26"},
412+ }
413+ output, err := NormalizeParameters(params)
414+ c.Check(err, gocheck.Equals, nil)
415+ c.Check(output, gocheck.Matches, "(a=1&z=26|z=26&a=1)")
416+}
417
418=== modified file 'usso.go'
419--- usso.go 2013-01-25 11:38:14 +0000
420+++ usso.go 2013-01-29 14:29:23 +0000
421@@ -1,13 +1,13 @@
422 package usso
423
424 import (
425+ "bytes"
426 "encoding/json"
427+ "fmt"
428 "io/ioutil"
429 "log"
430 "math/rand"
431 "net/http"
432- "net/url"
433- "strconv"
434 "strings"
435 "time"
436 )
437@@ -26,6 +26,12 @@
438 return server.baseUrl + "/api/v2/tokens"
439 }
440
441+// AccountURL returns the URL where the Ubuntu SSO account information can be
442+// requested.
443+func (server UbuntuSSOServer) AccountsURL() string {
444+ return server.baseUrl + "/api/v2/accounts/"
445+}
446+
447 // ProductionUbuntuSSOServer represents the production Ubuntu SSO server
448 // located at https://login.ubuntu.com.
449 var ProductionUbuntuSSOServer = UbuntuSSOServer{"https://login.ubuntu.com"}
450@@ -34,16 +40,10 @@
451 // at https://login.staging.ubuntu.com. Use it for testing.
452 var StagingUbuntuSSOServer = UbuntuSSOServer{"https://login.staging.ubuntu.com"}
453
454-type SSOData struct {
455- BaseURL string
456- ConsumerKey string `json:"consumer_key"`
457- ConsumerSecret string `json:"consumer_secret"`
458- TokenKey string `json:"token_key"`
459- TokenName string `json:"token_name"`
460- TokenSecret string `json:"token_secret"`
461-}
462-
463-func (server UbuntuSSOServer) GetToken(email string, password string, tokenName string) (*SSOData, error) {
464+// Giving user credentials and token name, retrieves oauth credentials
465+// for the users, the oauth credentials can be used later to sign requests.
466+func (server UbuntuSSOServer) GetToken(
467+ email string, password string, tokenName string) (*SSOData, error) {
468 credentials := map[string]string{
469 "email": email,
470 "password": password,
471@@ -75,17 +75,40 @@
472 return &ssodata, nil
473 }
474
475-func (oauth *SSOData) Sign(req *http.Request) error {
476- // Sign the provided request.
477- auth := `OAuth realm="API", ` +
478- `oauth_consumer_key="` + url.QueryEscape(oauth.ConsumerKey) + `", ` +
479- `oauth_token="` + url.QueryEscape(oauth.TokenKey) + `", ` +
480- `oauth_signature_method="PLAINTEXT", ` +
481- `oauth_signature="` + url.QueryEscape(
482- oauth.ConsumerSecret+`&`+oauth.TokenSecret) + `", ` +
483- `oauth_timestamp="` + strconv.FormatInt(time.Now().Unix(), 10) + `", ` +
484- `oauth_nonce="` + strconv.Itoa(int(rand.Intn(99999999))) + `", ` +
485- `oauth_version="1.0"`
486- req.Header.Add("Authorization", auth)
487- return nil
488+// Returns all the Ubuntu SSO information related to this account.
489+func (server UbuntuSSOServer) GetAccounts(ssodata *SSOData) (string, error) {
490+ ssodata.BaseURL = server.AccountsURL() + ssodata.ConsumerKey
491+ ssodata.HTTPMethod = "GET"
492+ ssodata.SignatureMethod = "HMAC-SHA1"
493+ request, err := http.NewRequest(ssodata.HTTPMethod, ssodata.BaseURL, nil)
494+ if err != nil {
495+ return "", err
496+ }
497+ err = SignRequest(ssodata, request)
498+ if err != nil {
499+ return "", err
500+ }
501+ client := &http.Client{}
502+ response, err := client.Do(request)
503+ if err != nil {
504+ fmt.Printf("Error: %s\n", err)
505+ }
506+ body, err := ioutil.ReadAll(response.Body)
507+ if err != nil {
508+ fmt.Println(err)
509+ }
510+ var b bytes.Buffer
511+ b.Write(body)
512+ return fmt.Sprint(b.String()), nil
513+}
514+
515+// Given oauth credentials and a request, return it signed.
516+func SignRequest(ssodata *SSOData, request *http.Request) error {
517+ return ssodata.SignRequest(request)
518+}
519+
520+// Given oauth credentials return a valid http authorization header.
521+func GetAuthorizationHeader(ssodata *SSOData) (string, error) {
522+ header, err := ssodata.GetAuthorizationHeader()
523+ return header, err
524 }
525
526=== modified file 'usso_test.go'
527--- usso_test.go 2013-01-25 11:38:14 +0000
528+++ usso_test.go 2013-01-29 14:29:23 +0000
529@@ -7,7 +7,6 @@
530 . "launchpad.net/gocheck"
531 "net/http"
532 "net/http/httptest"
533- "net/url"
534 "testing"
535 )
536
537@@ -46,7 +45,8 @@
538
539 // newSingleServingServer create a single-serving test http server which will
540 // return only one response as defined by the passed arguments.
541-func newSingleServingServer(uri string, response string, code int) *SingleServingServer {
542+func newSingleServingServer(
543+ uri string, response string, code int) *SingleServingServer {
544 var requestContent string
545 var requested bool
546 handler := func(w http.ResponseWriter, r *http.Request) {
547@@ -86,16 +86,19 @@
548 if err != nil {
549 panic(err)
550 }
551- server := newSingleServingServer("/api/v2/tokens", string(jsonServerResponseData), 200)
552+ server := newSingleServingServer("/api/v2/tokens",
553+ string(jsonServerResponseData), 200)
554 var testSSOServer = &UbuntuSSOServer{server.URL}
555 defer server.Close()
556
557 // The returned information is correct.
558 ssodata, err := testSSOServer.GetToken(email, password, tokenName)
559 c.Assert(err, IsNil)
560- expectedSSOData := &SSOData{ConsumerKey: consumerKey, ConsumerSecret: consumerSecret, TokenKey: tokenKey, TokenSecret: tokenSecret, TokenName: tokenName}
561+ expectedSSOData := &SSOData{ConsumerKey: consumerKey,
562+ ConsumerSecret: consumerSecret, TokenKey: tokenKey,
563+ TokenSecret: tokenSecret, TokenName: tokenName}
564 c.Assert(ssodata, DeepEquals, expectedSSOData)
565- // The request that the fake Ubuntu SSO Server got contained the credentials.
566+ //The request that the fake Ubuntu SSO Server got contained the credentials.
567 credentials := map[string]string{
568 "email": email,
569 "password": password,
570@@ -107,18 +110,3 @@
571 }
572 c.Assert(*server.requestContent, Equals, string(expectedRequestContent))
573 }
574-
575-func (suite *USSOTestSuite) TestSignRequestPlainText(c *C) {
576- baseUrl := "https://localhost"
577- ssoData := SSOData{BaseURL: baseUrl, ConsumerKey: consumerKey, ConsumerSecret: consumerSecret, TokenKey: tokenKey, TokenName: tokenName, TokenSecret: tokenSecret}
578- request, _ := http.NewRequest("GET", baseUrl, nil)
579-
580- err := ssoData.Sign(request)
581-
582- c.Assert(err, IsNil)
583- authHeader := request.Header["Authorization"][0]
584- c.Assert(authHeader, Matches, `.*OAuth realm="API".*`)
585- c.Assert(authHeader, Matches, `.*oauth_consumer_key="`+url.QueryEscape(ssoData.ConsumerKey)+`".*`)
586- c.Assert(authHeader, Matches, `.*oauth_token="`+url.QueryEscape(ssoData.TokenKey)+`".*`)
587- c.Assert(authHeader, Matches, `.*oauth_signature="`+url.QueryEscape(ssoData.ConsumerSecret+`&`+ssoData.TokenSecret)+`.*`)
588-}

Subscribers

People subscribed via source and target branches

to all changes: