-
Notifications
You must be signed in to change notification settings - Fork 2
/
connection.go
133 lines (121 loc) · 3.16 KB
/
connection.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package awql
import (
"database/sql/driver"
"encoding/json"
"io"
"net/http"
"net/url"
"strings"
"time"
)
const (
tokenURL = "https://accounts.google.com/o/oauth2/token"
tokenTimeout = time.Duration(4 * time.Second)
tokenExpiryDelta = 10 * time.Second
tokenExpiryDuration = 60 * time.Minute
)
// Conn represents a connection to a database and implements driver.Conn.
type Conn struct {
client *http.Client
adwordsID string
developerToken string
oAuth *Auth
opts *Opts
}
// Close marks this connection as no longer in use.
func (c *Conn) Close() error {
// Resets client
c.client = nil
return nil
}
// Begin is dedicated to start a transaction and awql does not support it.
func (c *Conn) Begin() (driver.Tx, error) {
return nil, driver.ErrSkip
}
// Prepare returns a prepared statement, bound to this connection.
func (c *Conn) Prepare(q string) (driver.Stmt, error) {
if q == "" {
// No query to prepare.
return nil, io.EOF
}
return &Stmt{Db: c, SrcQuery: q}, nil
}
// Auth returns an error if it can not download or parse the Google access token.
func (c *Conn) authenticate() error {
if c.oAuth == nil || c.oAuth.Valid() {
// Authentication is not required or already validated.
return nil
}
if !c.oAuth.IsSet() {
// No client information to refresh the token.
return ErrBadToken
}
d, err := c.downloadToken()
if err != nil {
return err
}
return c.retrieveToken(d)
}
// downloadToken calls Google Auth Api to retrieve an access token.
// @example Google Token
// {
// "access_token": "ya29.ExaMple",
// "token_type": "Bearer",
// "expires_in": 60
// }
func (c *Conn) downloadToken() (io.ReadCloser, error) {
rq, err := http.NewRequest(
"POST", tokenURL,
strings.NewReader(url.Values{
"client_id": {c.oAuth.ClientID},
"client_secret": {c.oAuth.ClientSecret},
"refresh_token": {c.oAuth.RefreshToken},
"grant_type": {"refresh_token"},
}.Encode()),
)
if err != nil {
return nil, err
}
c.client.Timeout = tokenTimeout
rq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// Retrieves an access token
resp, err := c.client.Do(rq)
if err != nil {
return nil, err
}
// Manages response in error
if resp.StatusCode != http.StatusOK {
switch resp.StatusCode {
case 0:
return nil, ErrNoNetwork
case http.StatusBadRequest:
return nil, ErrBadToken
default:
return nil, ErrBadNetwork
}
}
return resp.Body, nil
}
// retrieveToken parses the JSON response in order to map it to a AuthToken.
// An error occurs if the JSON is invalid.
func (c *Conn) retrieveToken(d io.ReadCloser) error {
var tk struct {
AccessToken string `json:"access_token"`
ExpiresInSec int `json:"expires_in"`
TokenType string `json:"token_type"`
}
defer d.Close()
err := json.NewDecoder(d).Decode(&tk)
if err != nil {
// Unable to parse the JSON response.
return ErrBadToken
}
if tk.ExpiresInSec == 0 || tk.AccessToken == "" {
// Invalid format of the token.
return ErrBadToken
}
c.oAuth.AccessToken = tk.AccessToken
c.oAuth.TokenType = tk.TokenType
c.oAuth.Expiry = time.Now().Add(time.Duration(tk.ExpiresInSec) * time.Second)
return nil
}