PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/oauth2/client.go

http://github.com/bradrydzewski/go.auth
Go | 249 lines | 128 code | 40 blank | 81 comment | 17 complexity | 38029c7d78b0349ba240ae9b9e705a40 MD5 | raw file
  1. package oauth2
  2. // see https://github.com/litl/rauth/blob/master/examples/twitter-timeline-cli.py
  3. // see http://tools.ietf.org/html/draft-ietf-oauth-v2-31
  4. import (
  5. "encoding/json"
  6. "io/ioutil"
  7. "net/http"
  8. "net/url"
  9. "strconv"
  10. "strings"
  11. )
  12. // Client represents an application making protected resource requests on
  13. // behalf of the resource owner and with its authorization.
  14. type Client struct {
  15. // The client_identifier issued to the client during the
  16. // registration process.
  17. ClientId string
  18. // The client_secret issued to the client during the
  19. // registration process.
  20. ClientSecret string
  21. // Used by the authorization server to return authorization credentials
  22. // responses to the client via the resource owner user-agent
  23. RedirectURL string
  24. // Used by the client to exchange an authorization grant for
  25. // an access token, typically with client authentication.
  26. AccessTokenURL string
  27. // Used by the client to obtain authorization from the resource
  28. // owner via user-agent redirection.
  29. AuthorizationURL string
  30. }
  31. // AuthorizeRedirect constructs the Authorization Endpoint, where the user
  32. // can authorize the client to access protected resources.
  33. func (c *Client) AuthorizeRedirect(scope, state string) string {
  34. // add required parameters
  35. params := make(url.Values)
  36. params.Add("response_type", ResponseTypeCode)
  37. //params.Set("redirect_uri", c.RedirectURL)
  38. params.Set("client_id", c.ClientId)
  39. // add optional redirect param
  40. // NOTE: this is optional for some providers, but not for others
  41. if len(c.RedirectURL) > 0 {
  42. params.Set("redirect_uri", c.RedirectURL)
  43. }
  44. // add optional scope param
  45. if len(scope) > 0 {
  46. params.Set("scope", scope)
  47. }
  48. // add optional state param
  49. if len(state) > 0 {
  50. params.Set("state", state)
  51. }
  52. // HACK: for google we must add access_type=offline in order
  53. // to obtain a refresh token
  54. if strings.HasPrefix(c.AuthorizationURL, "https://accounts.google.com") {
  55. params.Set("access_type", "offline")
  56. //params.Set("approval_prompt","force")
  57. }
  58. // generate the URL
  59. endpoint, _ := url.Parse(c.AuthorizationURL)
  60. endpoint.RawQuery = params.Encode()
  61. //HACK: Google separates scopes using a "+", however, these get
  62. // encoded and for some reason cause Google to fail the request.
  63. // So we will decode all plus signs to make the Google happy
  64. endpoint.RawQuery = strings.Replace(endpoint.RawQuery, "%2B", "+", -1)
  65. return endpoint.String()
  66. }
  67. // AuthorizeRedirect redirects an http.Request to the Authorizatioin Endpoint,
  68. // where the user can authorize the client to access protected resources.
  69. //func (c *Client) AuthorizeRedirect(w http.ResponseWriter, r *http.Request, scope, state string) {
  70. // http.Redirect(w, r, c.GetAuthorizeRedirect(scope, state), http.StatusSeeOther)
  71. //}
  72. // GrantToken will attempt to grant an Access Token using
  73. // the specified authorization code.
  74. func (c *Client) GrantToken(code string) (*Token, error) {
  75. params := make(url.Values)
  76. params.Set("grant_type", GrantTypeAuthorizationCode)
  77. params.Set("code", code)
  78. params.Set("scope", "")
  79. return c.grantToken(params)
  80. }
  81. // GrantTokenCredentials will attempt to grant an Access Token
  82. // for the Client to access protected resources the the Client owns.
  83. //
  84. // See http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.3
  85. func (c *Client) GrantTokenCredentials(scope string) (*Token, error) {
  86. params := make(url.Values)
  87. params.Set("grant_type", GrantTypeClientCredentials)
  88. params.Set("scope", scope)
  89. return c.grantToken(params)
  90. }
  91. // GrantTokenPassword will attempt to grant an Access Token using the
  92. // resource owner's credentials (username and password). The scope of
  93. // the access request may be optinally included, or left empty.
  94. //
  95. // See http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.3
  96. func (c *Client) GrantTokenPassword(username, password, scope string) (*Token, error) {
  97. params := make(url.Values)
  98. params.Set("grant_type", GrantTypePassword)
  99. params.Set("username", username)
  100. params.Set("password", password)
  101. params.Set("scope", scope)
  102. return c.grantToken(params)
  103. }
  104. // RefreshToken requests a new access token by authenticating with
  105. // the authorization server and presenting the refresh token.
  106. func (c *Client) RefreshToken(refreshToken string) (*Token, error) {
  107. params := make(url.Values)
  108. params.Set("grant_type", GrantTypeRefreshToken)
  109. params.Set("refresh_token", refreshToken)
  110. params.Set("scope", "")
  111. return c.grantToken(params)
  112. }
  113. // Token represents a successful response to an OAuth2.0 Access
  114. // Token Request, including a Refresh Token request.
  115. type Token struct {
  116. // The access token issued by the authorization server.
  117. AccessToken string `json:"access_token"`
  118. // The type of the token issued (bearer, mac, etc)
  119. TokenType string `json:"token_type"`
  120. // The refresh token, which can be used to obtain new
  121. // access tokens using the same authorization grant
  122. RefreshToken string `json:"refresh_token"`
  123. // The lifetime in seconds of the access token. For
  124. // example, the value "3600" denotes that the access token will
  125. // expire in one hour from the time the response was generated.
  126. ExpiresIn int64 `json:"expires_in"`
  127. // The scope of the access token.
  128. Scope string
  129. }
  130. func (t Token) Token() string {
  131. return t.AccessToken
  132. }
  133. // Error represents a failed request to the OAuth2.0 Authorization
  134. // or Resource server.
  135. type Error struct {
  136. // A single ASCII [USASCII] error code
  137. Code string `json:"error"`
  138. // A human-readable ASCII [USASCII] text providing
  139. // additional information, used to assist the client developer in
  140. // understanding the error that occurred.
  141. Description string `json:"error_description"`
  142. // A URI identifying a human-readable web page with
  143. // information about the error, used to provide the client
  144. // developer with additional information about the error.
  145. URI string `json:"error_uri"`
  146. }
  147. // Error returns a string representation of the OAuth2
  148. // error message.
  149. func (e Error) Error() string {
  150. return e.Code
  151. }
  152. // helper function to retrieve a token from the server
  153. func (c *Client) grantToken(params url.Values) (*Token, error) {
  154. // Create the access token url params
  155. if params == nil {
  156. params = make(url.Values)
  157. }
  158. // Add the client id, client secret and code to the query params
  159. params.Set("client_id", c.ClientId)
  160. params.Set("client_secret", c.ClientSecret)
  161. params.Set("redirect_uri", c.RedirectURL)
  162. // Create the access token request url
  163. endpoint, _ := url.Parse(c.AccessTokenURL)
  164. // Create the http request
  165. req := http.Request{
  166. URL: endpoint,
  167. Method: "POST",
  168. ProtoMajor: 1,
  169. ProtoMinor: 1,
  170. Close: true,
  171. }
  172. // Encode the URL paraemeters in the Body of the Request
  173. encParams := params.Encode()
  174. reader := strings.NewReader(encParams)
  175. req.Body = ioutil.NopCloser(reader)
  176. // Add the header params
  177. header := make(http.Header)
  178. header.Set("Accept", "application/json")
  179. header.Set("Content-Type", "application/x-www-form-urlencoded")
  180. header.Set("Content-length", strconv.Itoa(len(encParams)))
  181. req.Header = header
  182. // Do the http request and get the response
  183. resp, err := http.DefaultClient.Do(&req)
  184. if err != nil {
  185. return nil, err
  186. }
  187. // Get the response body
  188. raw, err := ioutil.ReadAll(resp.Body)
  189. defer resp.Body.Close()
  190. if err != nil {
  191. return nil, err
  192. }
  193. // Unmarshal the json response body to get the token
  194. token := Token{}
  195. if err := json.Unmarshal(raw, &token); err != nil {
  196. return nil, err
  197. }
  198. // If no access token is provided it must be an error. Normally
  199. // we would check the StatusCode, however, some providers return
  200. // a 200 Status OK even if there is an error :(
  201. if len(token.AccessToken) == 0 {
  202. oauthError := Error{}
  203. if err := json.Unmarshal(raw, &oauthError); err != nil {
  204. return nil, err
  205. }
  206. return nil, oauthError
  207. }
  208. return &token, nil
  209. }