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
=== modified file 'example/usso_example.go'
--- example/usso_example.go 2013-01-21 16:57:31 +0000
+++ example/usso_example.go 2013-01-29 14:29:23 +0000
@@ -1,23 +1,27 @@
1package main1package main
22
3import (3import (
4 "bytes"
4 "encoding/json"5 "encoding/json"
5 "fmt"6 "fmt"
7 "io/ioutil"
6 "launchpad.net/usso"8 "launchpad.net/usso"
9 "net/http"
7)10)
811
9var email string12var email, password, tokenName, signature_method string
10var password string
11var tokenName string
1213
13func inputParams() {14func inputParams() {
14 fmt.Println("This application will query the staging Ubuntu SSO Server to fetch authorisation tokens.")15 fmt.Println("This application will query the staging Ubuntu SSO Server" +
16 " to fetch authorisation tokens.")
15 fmt.Print("Enter email: ")17 fmt.Print("Enter email: ")
16 fmt.Scanf("%s", &email)18 fmt.Scanf("%s", &email)
17 fmt.Print("Enter password: ")19 fmt.Print("Enter password: ")
18 fmt.Scanf("%s", &password)20 fmt.Scanf("%s", &password)
19 fmt.Print("Enter token name: ")21 fmt.Print("Enter token name: ")
20 fmt.Scanf("%s", &tokenName)22 fmt.Scanf("%s", &tokenName)
23 fmt.Print("Enter signature method (PLAINTEXT or HMAC-SHA1): ")
24 fmt.Scanf("%s", &signature_method)
21}25}
2226
23func main() {27func main() {
@@ -26,16 +30,46 @@
26 // Fetch the tokens using usso.GetToken.30 // Fetch the tokens using usso.GetToken.
27 fmt.Println("Fetching tokens from staging server...")31 fmt.Println("Fetching tokens from staging server...")
28 server := usso.StagingUbuntuSSOServer32 server := usso.StagingUbuntuSSOServer
29 // One would use server := usso.ProductionUbuntuSSOServer to use the production Ubuntu SSO Server.33 // One would use server := usso.ProductionUbuntuSSOServer
30 token, err := server.GetToken(email, password, tokenName)34 // to use the production Ubuntu SSO Server.
35 ssodata, err := server.GetToken(email, password, tokenName)
31 if err != nil {36 if err != nil {
32 panic(err)37 panic(err)
33 }38 }
34 // Format the result as json for displaying it:39 // Format the result as json for displaying it:
35 json_token, err := json.Marshal(token)40 json_token, err := json.Marshal(ssodata)
36 if err != nil {41 if err != nil {
37 panic(err)42 panic(err)
38 }43 }
39 fmt.Printf("Got tokens: %s\n", json_token)44 fmt.Printf("Got tokens: %s\n", json_token)
4045
46 // This would be the easiest way to get the account data.
47 //accounts, _ := server.GetAccounts(ssodata)
48 //fmt.Printf("Got accounts info: %s\n", accounts)
49
50 // But this shows how to sign a generic request.
51 ssodata.BaseURL = fmt.Sprintf(
52 "https://login.staging.ubuntu.com/api/v2/accounts/%s",
53 ssodata.ConsumerKey)
54 ssodata.HTTPMethod = "GET"
55 ssodata.SignatureMethod = signature_method
56 request, _ := http.NewRequest(ssodata.HTTPMethod, ssodata.BaseURL, nil)
57 usso.SignRequest(ssodata, request)
58
59 if err != nil {
60 fmt.Printf("Error: %s\n", err)
61 }
62 // run the request
63 client := &http.Client{}
64 response, err := client.Do(request)
65 if err != nil {
66 fmt.Printf("Error: %s\n", err)
67 }
68 body, err := ioutil.ReadAll(response.Body)
69 if err != nil {
70 fmt.Println(err)
71 }
72 var b bytes.Buffer
73 b.Write(body)
74 fmt.Printf("response: %+v\n", b.String())
41}75}
4276
=== added file 'oauth.go'
--- oauth.go 1970-01-01 00:00:00 +0000
+++ oauth.go 2013-01-29 14:29:23 +0000
@@ -0,0 +1,126 @@
1package usso
2
3import (
4 "crypto/hmac"
5 "crypto/sha1"
6 "encoding/base64"
7 "errors"
8 "fmt"
9 "math/rand"
10 "net/http"
11 "net/url"
12 "strconv"
13 "time"
14)
15
16// Initialize the random generator.
17func init() {
18 rand.Seed(time.Now().UTC().UnixNano())
19}
20
21// Create a timestamp used in authorization header.
22func timestamp() string {
23 return strconv.Itoa(int(time.Now().Unix()))
24}
25
26// Create a nonce used in authorization header.
27func nonce() string {
28 return strconv.Itoa(rand.Intn(100000000))
29}
30
31// Contains the oauth data to perform a request.
32type SSOData struct {
33 HTTPMethod string `json:"-"`
34 BaseURL string `json:"-"`
35 Params url.Values `json:"-"`
36 Nonce string `json:"-"`
37 Timestamp string `json:"-"`
38 SignatureMethod string `json:"-"`
39 ConsumerKey string `json:"consumer_key"`
40 ConsumerSecret string `json:"consumer_secret"`
41 TokenKey string `json:"token_key"`
42 TokenName string `json:"token_name"`
43 TokenSecret string `json:"token_secret"`
44}
45
46// Depending on the signature method, create the signature from the
47// consumer secret, the token secret and, if required, the URL.
48// Supported signature methods are PLAINTEXT and HMAC-SHA1.
49func (oauth *SSOData) signature() (string, error) {
50 switch oauth.SignatureMethod {
51 case "PLAINTEXT":
52 return fmt.Sprintf(
53 `%s%%26%s`,
54 oauth.ConsumerSecret,
55 oauth.TokenSecret), nil
56 case "HMAC-SHA1":
57 base_url, err := NormalizeURL(oauth.BaseURL)
58 if err != nil {
59 return "", err
60 }
61 params, err := NormalizeParameters(oauth.Params)
62 if err != nil {
63 return "", err
64 }
65 base_string := fmt.Sprintf(`%s&%s&%s%s%s%s%s%s%s`,
66 oauth.HTTPMethod,
67 url.QueryEscape(base_url),
68 url.QueryEscape(params),
69 url.QueryEscape("oauth_consumer_key="+oauth.ConsumerKey),
70 url.QueryEscape("&oauth_nonce="+oauth.Nonce),
71 url.QueryEscape("&oauth_signature_method="+oauth.SignatureMethod),
72 url.QueryEscape("&oauth_timestamp="+oauth.Timestamp),
73 url.QueryEscape("&oauth_token="+oauth.TokenKey),
74 url.QueryEscape("&oauth_version=1.0"))
75 hashfun := hmac.New(sha1.New, []byte(
76 oauth.ConsumerSecret+"&"+oauth.TokenSecret))
77 hashfun.Write([]byte(base_string))
78 rawsignature := hashfun.Sum(nil)
79 base64signature := make(
80 []byte, base64.StdEncoding.EncodedLen(len(rawsignature)))
81 base64.StdEncoding.Encode(base64signature, rawsignature)
82 return string(base64signature), nil
83 default:
84 return "", errors.New(
85 "usso/oauth: Oauth Signature Method not supported.")
86 }
87 return "", nil
88}
89
90// Sign the provided request.
91func (oauth *SSOData) GetAuthorizationHeader() (string, error) {
92 if oauth.Nonce == "" {
93 oauth.Nonce = nonce()
94 }
95 if oauth.Timestamp == "" {
96 oauth.Timestamp = timestamp()
97 }
98 signature, err := oauth.signature()
99 if err != nil {
100 return "", err
101 }
102 auth := fmt.Sprintf(
103 `OAuth realm="API", `+
104 `oauth_consumer_key="%s", `+
105 `oauth_token="%s", `+
106 `oauth_signature_method="%s", `+
107 `oauth_signature="%s", `+
108 `oauth_timestamp="%s", `+
109 `oauth_nonce="%s", `+
110 `oauth_version="1.0"`,
111 url.QueryEscape(oauth.ConsumerKey),
112 url.QueryEscape(oauth.TokenKey),
113 oauth.SignatureMethod,
114 signature,
115 url.QueryEscape(oauth.Timestamp),
116 url.QueryEscape(oauth.Nonce))
117
118 return auth, nil
119}
120
121// Sign the provided request.
122func (oauth *SSOData) SignRequest(req *http.Request) error {
123 auth, error := oauth.GetAuthorizationHeader()
124 req.Header.Add("Authorization", auth)
125 return error
126}
0127
=== added file 'oauth_test.go'
--- oauth_test.go 1970-01-01 00:00:00 +0000
+++ oauth_test.go 2013-01-29 14:29:23 +0000
@@ -0,0 +1,52 @@
1package usso
2
3import (
4 . "launchpad.net/gocheck"
5 "net/http"
6 "net/url"
7)
8
9func (suite *USSOTestSuite) TestSignRequestPlainText(c *C) {
10 baseUrl := "https://localhost"
11 ssodata := SSOData{BaseURL: baseUrl, ConsumerKey: consumerKey,
12 ConsumerSecret: consumerSecret, TokenKey: tokenKey,
13 TokenName: tokenName, TokenSecret: tokenSecret}
14 request, _ := http.NewRequest("GET", baseUrl, nil)
15 ssodata.HTTPMethod = "GET"
16 ssodata.SignatureMethod = "PLAINTEXT"
17 err := ssodata.SignRequest(request)
18 c.Assert(err, IsNil)
19 authHeader := request.Header["Authorization"][0]
20 c.Assert(authHeader, Matches, `^OAuth.*`)
21 c.Assert(authHeader, Matches, `.*realm="API".*`)
22 c.Assert(authHeader, Matches,
23 `.*oauth_consumer_key="`+url.QueryEscape(ssodata.ConsumerKey)+`".*`)
24 c.Assert(authHeader, Matches,
25 `.*oauth_token="`+url.QueryEscape(ssodata.TokenKey)+`".*`)
26 c.Assert(authHeader, Matches,
27 `.*oauth_signature="`+url.QueryEscape(
28 ssodata.ConsumerSecret+`&`+ssodata.TokenSecret)+`.*`)
29}
30
31// Test the request signing with oauth_signature_method = SHA1
32func (suite *USSOTestSuite) TestSignRequestSHA1(c *C) {
33 baseUrl := "https://localhost"
34 ssodata := SSOData{BaseURL: baseUrl, ConsumerKey: consumerKey,
35 ConsumerSecret: consumerSecret, TokenKey: tokenKey,
36 TokenName: tokenName, TokenSecret: tokenSecret,
37 Nonce: "10888885", Timestamp: "1358853126"}
38 request, _ := http.NewRequest("GET", baseUrl, nil)
39 ssodata.HTTPMethod = "GET"
40 ssodata.SignatureMethod = "HMAC-SHA1"
41 err := ssodata.SignRequest(request)
42 c.Assert(err, IsNil)
43 authHeader := request.Header["Authorization"][0]
44 c.Assert(authHeader, Matches, `^OAuth.*`)
45 c.Assert(authHeader, Matches, `.*realm="API".*`)
46 c.Assert(authHeader, Matches,
47 `.*oauth_consumer_key="`+url.QueryEscape(ssodata.ConsumerKey)+`".*`)
48 c.Assert(authHeader, Matches,
49 `.*oauth_token="`+url.QueryEscape(ssodata.TokenKey)+`".*`)
50 c.Assert(authHeader, Matches,
51 `.*oauth_signature="`+"amJnYeek4G9ObTgTiE2y6cwTyPg="+`.*`)
52}
053
=== added file 'url.go'
--- url.go 1970-01-01 00:00:00 +0000
+++ url.go 2013-01-29 14:29:23 +0000
@@ -0,0 +1,45 @@
1package usso
2
3import (
4 "fmt"
5 "net/url"
6 "strings"
7)
8
9// Remove the standard ports from the URL.
10func normalizeHost(scheme, host_spec string) string {
11 standard_ports := map[string]string{
12 "http": "80",
13 "https": "443",
14 }
15 host_parts := strings.Split(host_spec, ":")
16 if len(host_parts) == 2 && host_parts[1] == standard_ports[scheme] {
17 // There's a port, but it's the default one. Leave it out.
18 return host_parts[0]
19 }
20 return host_spec
21}
22
23// Normalize the URL according to OAuth specs.
24func NormalizeURL(input_url string) (string, error) {
25 parsed_url, err := url.Parse(input_url)
26 if err != nil {
27 return "", err
28 }
29
30 host := normalizeHost(parsed_url.Scheme, parsed_url.Host)
31 normalized_url := fmt.Sprintf(
32 "%v://%v%v", parsed_url.Scheme, host, parsed_url.Path)
33 return normalized_url, nil
34}
35
36// Normalize the parameters in the query string according to OAuth specs.
37func NormalizeParameters(parameters url.Values) (string, error) {
38 filtered_map := make(url.Values, len(parameters))
39 for param, value := range parameters {
40 if param != "oauth_signature" {
41 filtered_map[param] = value
42 }
43 }
44 return filtered_map.Encode(), nil
45}
046
=== added file 'url_test.go'
--- url_test.go 1970-01-01 00:00:00 +0000
+++ url_test.go 2013-01-29 14:29:23 +0000
@@ -0,0 +1,88 @@
1package usso
2
3import (
4 "launchpad.net/gocheck"
5 "net/url"
6)
7
8// When NormalizeURL() is passed a simple URL, it will make no changes
9// to it.
10func (suite *USSOTestSuite) TestNormalizeURLReturnsBasicURL(c *gocheck.C) {
11 output, err := NormalizeURL("http://example.com/path")
12 c.Check(err, gocheck.Equals, nil)
13 c.Check(output, gocheck.Equals, "http://example.com/path")
14}
15
16// NormalizeURL() strips the ":80" from http:// URLs that contain it.
17func (suite *USSOTestSuite) TestNormalizeURLStripsStandardHTTPPort(
18 c *gocheck.C) {
19 output, err := NormalizeURL("http://example.com:80/path")
20 c.Check(err, gocheck.Equals, nil)
21 c.Check(output, gocheck.Equals, "http://example.com/path")
22}
23
24// NormalizeURL() strips the ":443" from https:// URLs that contain it.
25func (suite *USSOTestSuite) TestNormalizeURLStripsStandardHTTPSPort(
26 c *gocheck.C) {
27 output, err := NormalizeURL("https://example.com:443/path")
28 c.Check(err, gocheck.Equals, nil)
29 c.Check(output, gocheck.Equals, "https://example.com/path")
30}
31
32// NormalizeURL() does not remove non-standard ports from the URL.
33func (suite *USSOTestSuite) TestNormalizeURLLeavesNonstandardPort(
34 c *gocheck.C) {
35 output, err := NormalizeURL("http://example.com:8080/")
36 c.Check(err, gocheck.Equals, nil)
37 c.Check(output, gocheck.Equals, "http://example.com:8080/")
38}
39
40// NormalizeURL() strips the query string from URLs.
41func (suite *USSOTestSuite) TestNormalizeURLStripsParameters(c *gocheck.C) {
42 output, err := NormalizeURL("http://example.com/path?query=value&param=arg")
43 c.Check(err, gocheck.Equals, nil)
44 c.Check(output, gocheck.Equals, "http://example.com/path")
45}
46
47// NormalizeParameters() takes a url.Values instance and returns an
48// encoded key=value string containing the parameters in that instance.
49func (suite *USSOTestSuite) TestNormalizeParametersReturnsParameters(
50 c *gocheck.C) {
51 output, err := NormalizeParameters(url.Values{"param": []string{"value"}})
52 c.Check(err, gocheck.Equals, nil)
53 c.Check(output, gocheck.Equals, "param=value")
54}
55
56// NormalizeParameters() encodes multiple key/value parameters as a
57// query string.
58func (suite *USSOTestSuite) TestNormalizeParametersConcatenatesParameters(
59 c *gocheck.C) {
60 output, err := NormalizeParameters(
61 url.Values{"a": []string{"1"}, "b": []string{"2"}})
62 c.Check(err, gocheck.Equals, nil)
63 c.Check(output, gocheck.Matches, "(a=1&b=2|b=2&a=1)")
64}
65
66// NormalizeParameters() escapes the parameters correctly when encoding
67// them as a query string.
68func (suite *USSOTestSuite) TestNormalizeParametersEscapesParameters(
69 c *gocheck.C) {
70 output, err := NormalizeParameters(url.Values{"a&b": []string{"1"}})
71 c.Check(err, gocheck.Equals, nil)
72 c.Check(output, gocheck.Equals, "a%26b=1")
73}
74
75// If oauth_signature appears in the parameters passed to
76// NormalizeParameters(), it is omitted in the returned string as it does not
77// have to be included in the computation of the new oauth_signature.
78func (suite *USSOTestSuite) TestNormalizeParametersOmitsOAuthSignature(
79 c *gocheck.C) {
80 params := url.Values{
81 "a": []string{"1"},
82 "oauth_signature": []string{"foobarsplatszot"},
83 "z": []string{"26"},
84 }
85 output, err := NormalizeParameters(params)
86 c.Check(err, gocheck.Equals, nil)
87 c.Check(output, gocheck.Matches, "(a=1&z=26|z=26&a=1)")
88}
089
=== modified file 'usso.go'
--- usso.go 2013-01-25 11:38:14 +0000
+++ usso.go 2013-01-29 14:29:23 +0000
@@ -1,13 +1,13 @@
1package usso1package usso
22
3import (3import (
4 "bytes"
4 "encoding/json"5 "encoding/json"
6 "fmt"
5 "io/ioutil"7 "io/ioutil"
6 "log"8 "log"
7 "math/rand"9 "math/rand"
8 "net/http"10 "net/http"
9 "net/url"
10 "strconv"
11 "strings"11 "strings"
12 "time"12 "time"
13)13)
@@ -26,6 +26,12 @@
26 return server.baseUrl + "/api/v2/tokens"26 return server.baseUrl + "/api/v2/tokens"
27}27}
2828
29// AccountURL returns the URL where the Ubuntu SSO account information can be
30// requested.
31func (server UbuntuSSOServer) AccountsURL() string {
32 return server.baseUrl + "/api/v2/accounts/"
33}
34
29// ProductionUbuntuSSOServer represents the production Ubuntu SSO server35// ProductionUbuntuSSOServer represents the production Ubuntu SSO server
30// located at https://login.ubuntu.com.36// located at https://login.ubuntu.com.
31var ProductionUbuntuSSOServer = UbuntuSSOServer{"https://login.ubuntu.com"}37var ProductionUbuntuSSOServer = UbuntuSSOServer{"https://login.ubuntu.com"}
@@ -34,16 +40,10 @@
34// at https://login.staging.ubuntu.com. Use it for testing.40// at https://login.staging.ubuntu.com. Use it for testing.
35var StagingUbuntuSSOServer = UbuntuSSOServer{"https://login.staging.ubuntu.com"}41var StagingUbuntuSSOServer = UbuntuSSOServer{"https://login.staging.ubuntu.com"}
3642
37type SSOData struct {43// Giving user credentials and token name, retrieves oauth credentials
38 BaseURL string44// for the users, the oauth credentials can be used later to sign requests.
39 ConsumerKey string `json:"consumer_key"`45func (server UbuntuSSOServer) GetToken(
40 ConsumerSecret string `json:"consumer_secret"`46 email string, password string, tokenName string) (*SSOData, error) {
41 TokenKey string `json:"token_key"`
42 TokenName string `json:"token_name"`
43 TokenSecret string `json:"token_secret"`
44}
45
46func (server UbuntuSSOServer) GetToken(email string, password string, tokenName string) (*SSOData, error) {
47 credentials := map[string]string{47 credentials := map[string]string{
48 "email": email,48 "email": email,
49 "password": password,49 "password": password,
@@ -75,17 +75,40 @@
75 return &ssodata, nil75 return &ssodata, nil
76}76}
7777
78func (oauth *SSOData) Sign(req *http.Request) error {78// Returns all the Ubuntu SSO information related to this account.
79 // Sign the provided request.79func (server UbuntuSSOServer) GetAccounts(ssodata *SSOData) (string, error) {
80 auth := `OAuth realm="API", ` +80 ssodata.BaseURL = server.AccountsURL() + ssodata.ConsumerKey
81 `oauth_consumer_key="` + url.QueryEscape(oauth.ConsumerKey) + `", ` +81 ssodata.HTTPMethod = "GET"
82 `oauth_token="` + url.QueryEscape(oauth.TokenKey) + `", ` +82 ssodata.SignatureMethod = "HMAC-SHA1"
83 `oauth_signature_method="PLAINTEXT", ` +83 request, err := http.NewRequest(ssodata.HTTPMethod, ssodata.BaseURL, nil)
84 `oauth_signature="` + url.QueryEscape(84 if err != nil {
85 oauth.ConsumerSecret+`&`+oauth.TokenSecret) + `", ` +85 return "", err
86 `oauth_timestamp="` + strconv.FormatInt(time.Now().Unix(), 10) + `", ` +86 }
87 `oauth_nonce="` + strconv.Itoa(int(rand.Intn(99999999))) + `", ` +87 err = SignRequest(ssodata, request)
88 `oauth_version="1.0"`88 if err != nil {
89 req.Header.Add("Authorization", auth)89 return "", err
90 return nil90 }
91 client := &http.Client{}
92 response, err := client.Do(request)
93 if err != nil {
94 fmt.Printf("Error: %s\n", err)
95 }
96 body, err := ioutil.ReadAll(response.Body)
97 if err != nil {
98 fmt.Println(err)
99 }
100 var b bytes.Buffer
101 b.Write(body)
102 return fmt.Sprint(b.String()), nil
103}
104
105// Given oauth credentials and a request, return it signed.
106func SignRequest(ssodata *SSOData, request *http.Request) error {
107 return ssodata.SignRequest(request)
108}
109
110// Given oauth credentials return a valid http authorization header.
111func GetAuthorizationHeader(ssodata *SSOData) (string, error) {
112 header, err := ssodata.GetAuthorizationHeader()
113 return header, err
91}114}
92115
=== modified file 'usso_test.go'
--- usso_test.go 2013-01-25 11:38:14 +0000
+++ usso_test.go 2013-01-29 14:29:23 +0000
@@ -7,7 +7,6 @@
7 . "launchpad.net/gocheck"7 . "launchpad.net/gocheck"
8 "net/http"8 "net/http"
9 "net/http/httptest"9 "net/http/httptest"
10 "net/url"
11 "testing"10 "testing"
12)11)
1312
@@ -46,7 +45,8 @@
4645
47// newSingleServingServer create a single-serving test http server which will46// newSingleServingServer create a single-serving test http server which will
48// return only one response as defined by the passed arguments.47// return only one response as defined by the passed arguments.
49func newSingleServingServer(uri string, response string, code int) *SingleServingServer {48func newSingleServingServer(
49 uri string, response string, code int) *SingleServingServer {
50 var requestContent string50 var requestContent string
51 var requested bool51 var requested bool
52 handler := func(w http.ResponseWriter, r *http.Request) {52 handler := func(w http.ResponseWriter, r *http.Request) {
@@ -86,16 +86,19 @@
86 if err != nil {86 if err != nil {
87 panic(err)87 panic(err)
88 }88 }
89 server := newSingleServingServer("/api/v2/tokens", string(jsonServerResponseData), 200)89 server := newSingleServingServer("/api/v2/tokens",
90 string(jsonServerResponseData), 200)
90 var testSSOServer = &UbuntuSSOServer{server.URL}91 var testSSOServer = &UbuntuSSOServer{server.URL}
91 defer server.Close()92 defer server.Close()
9293
93 // The returned information is correct.94 // The returned information is correct.
94 ssodata, err := testSSOServer.GetToken(email, password, tokenName)95 ssodata, err := testSSOServer.GetToken(email, password, tokenName)
95 c.Assert(err, IsNil)96 c.Assert(err, IsNil)
96 expectedSSOData := &SSOData{ConsumerKey: consumerKey, ConsumerSecret: consumerSecret, TokenKey: tokenKey, TokenSecret: tokenSecret, TokenName: tokenName}97 expectedSSOData := &SSOData{ConsumerKey: consumerKey,
98 ConsumerSecret: consumerSecret, TokenKey: tokenKey,
99 TokenSecret: tokenSecret, TokenName: tokenName}
97 c.Assert(ssodata, DeepEquals, expectedSSOData)100 c.Assert(ssodata, DeepEquals, expectedSSOData)
98 // The request that the fake Ubuntu SSO Server got contained the credentials.101 //The request that the fake Ubuntu SSO Server got contained the credentials.
99 credentials := map[string]string{102 credentials := map[string]string{
100 "email": email,103 "email": email,
101 "password": password,104 "password": password,
@@ -107,18 +110,3 @@
107 }110 }
108 c.Assert(*server.requestContent, Equals, string(expectedRequestContent))111 c.Assert(*server.requestContent, Equals, string(expectedRequestContent))
109}112}
110
111func (suite *USSOTestSuite) TestSignRequestPlainText(c *C) {
112 baseUrl := "https://localhost"
113 ssoData := SSOData{BaseURL: baseUrl, ConsumerKey: consumerKey, ConsumerSecret: consumerSecret, TokenKey: tokenKey, TokenName: tokenName, TokenSecret: tokenSecret}
114 request, _ := http.NewRequest("GET", baseUrl, nil)
115
116 err := ssoData.Sign(request)
117
118 c.Assert(err, IsNil)
119 authHeader := request.Header["Authorization"][0]
120 c.Assert(authHeader, Matches, `.*OAuth realm="API".*`)
121 c.Assert(authHeader, Matches, `.*oauth_consumer_key="`+url.QueryEscape(ssoData.ConsumerKey)+`".*`)
122 c.Assert(authHeader, Matches, `.*oauth_token="`+url.QueryEscape(ssoData.TokenKey)+`".*`)
123 c.Assert(authHeader, Matches, `.*oauth_signature="`+url.QueryEscape(ssoData.ConsumerSecret+`&`+ssoData.TokenSecret)+`.*`)
124}

Subscribers

People subscribed via source and target branches

to all changes: