David K. Bainbridge | 528b318 | 2017-01-23 08:51:59 -0800 | [diff] [blame] | 1 | // Copyright 2012-2016 Canonical Ltd. |
| 2 | // Licensed under the LGPLv3, see LICENCE file for details. |
| 3 | |
| 4 | package gomaasapi |
| 5 | |
| 6 | import ( |
| 7 | "crypto/rand" |
| 8 | "fmt" |
| 9 | "net/http" |
| 10 | "net/url" |
| 11 | "strconv" |
| 12 | "strings" |
| 13 | "time" |
| 14 | ) |
| 15 | |
| 16 | // Not a true uuidgen, but at least creates same length random |
| 17 | func generateNonce() (string, error) { |
| 18 | randBytes := make([]byte, 16) |
| 19 | _, err := rand.Read(randBytes) |
| 20 | if err != nil { |
| 21 | return "", err |
| 22 | } |
| 23 | return fmt.Sprintf("%16x", randBytes), nil |
| 24 | } |
| 25 | |
| 26 | func generateTimestamp() string { |
| 27 | return strconv.Itoa(int(time.Now().Unix())) |
| 28 | } |
| 29 | |
| 30 | type OAuthSigner interface { |
| 31 | OAuthSign(request *http.Request) error |
| 32 | } |
| 33 | |
| 34 | type OAuthToken struct { |
| 35 | ConsumerKey string |
| 36 | ConsumerSecret string |
| 37 | TokenKey string |
| 38 | TokenSecret string |
| 39 | } |
| 40 | |
| 41 | // Trick to ensure *plainTextOAuthSigner implements the OAuthSigner interface. |
| 42 | var _ OAuthSigner = (*plainTextOAuthSigner)(nil) |
| 43 | |
| 44 | type plainTextOAuthSigner struct { |
| 45 | token *OAuthToken |
| 46 | realm string |
| 47 | } |
| 48 | |
| 49 | func NewPlainTestOAuthSigner(token *OAuthToken, realm string) (OAuthSigner, error) { |
| 50 | return &plainTextOAuthSigner{token, realm}, nil |
| 51 | } |
| 52 | |
| 53 | // OAuthSignPLAINTEXT signs the provided request using the OAuth PLAINTEXT |
| 54 | // method: http://oauth.net/core/1.0/#anchor22. |
| 55 | func (signer plainTextOAuthSigner) OAuthSign(request *http.Request) error { |
| 56 | |
| 57 | signature := signer.token.ConsumerSecret + `&` + signer.token.TokenSecret |
| 58 | nonce, err := generateNonce() |
| 59 | if err != nil { |
| 60 | return err |
| 61 | } |
| 62 | authData := map[string]string{ |
| 63 | "realm": signer.realm, |
| 64 | "oauth_consumer_key": signer.token.ConsumerKey, |
| 65 | "oauth_token": signer.token.TokenKey, |
| 66 | "oauth_signature_method": "PLAINTEXT", |
| 67 | "oauth_signature": signature, |
| 68 | "oauth_timestamp": generateTimestamp(), |
| 69 | "oauth_nonce": nonce, |
| 70 | "oauth_version": "1.0", |
| 71 | } |
| 72 | // Build OAuth header. |
| 73 | var authHeader []string |
| 74 | for key, value := range authData { |
| 75 | authHeader = append(authHeader, fmt.Sprintf(`%s="%s"`, key, url.QueryEscape(value))) |
| 76 | } |
| 77 | strHeader := "OAuth " + strings.Join(authHeader, ", ") |
| 78 | request.Header.Add("Authorization", strHeader) |
| 79 | return nil |
| 80 | } |