forked from prasmussen/gdrive
-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
listener.go
149 lines (131 loc) · 4.16 KB
/
listener.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package auth
import (
"context"
"fmt"
"net"
"net/http"
"os"
"time"
"golang.org/x/oauth2"
)
type authorize struct{ authUrl string }
type callback struct {
done chan string
bad chan bool
state string
}
func (a authorize) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.Header().Add("Location", a.authUrl)
w.WriteHeader(302)
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Redirect to authentication server</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintf(w, "Click <a href=\"%s\">here</a> to authorize gdrive to use Google Drive\n",
a.authUrl)
fmt.Fprintln(w, "</body></html>")
}
func (c callback) ServeHTTP(w http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if err != nil {
fmt.Printf("Could not parse form on /callback: %s\n", err)
w.WriteHeader(400)
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Bad request</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintln(w, "Bad request: Missing authentication response")
fmt.Fprintln(w, "</body></html>")
return
}
if req.Form.Has("error") {
fmt.Printf("authentication failed, server response is %s\n", req.Form.Get("error"))
c.bad <- true
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Google Drive authentication failed</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintf(w, "Authentication failed or refused: %s\n", req.Form.Get("error"))
fmt.Fprintln(w, "</body></html>")
return
}
if !req.Form.Has("code") || !req.Form.Has("state") {
fmt.Println("callback request is missing parameters")
w.WriteHeader(400)
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Bad request</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintln(w, "Bad request: response is missing the code or state parameters")
fmt.Fprintln(w, "</body></html>")
return
}
code := req.Form.Get("code")
state := req.Form.Get("state")
if state != c.state {
fmt.Printf("Callback state mismatch: %s vs %s", state, c.state)
w.WriteHeader(400)
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Bad request</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintln(w, "Bad request: response state mismatch")
fmt.Fprintln(w, "</body></html>")
return
}
fmt.Fprintln(w, "<html><head>")
fmt.Fprintln(w, "<title>Authentication response received</title>")
fmt.Fprintln(w, "</head><body>")
fmt.Fprintln(w, "Authentication response has been received. Check the terminal where gdrive is running")
fmt.Fprintln(w, "</body></html>")
c.done <- code
}
func AuthCodeHTTP(conf *oauth2.Config, state, challenge string) (func() (string, error), error) {
authChallengeMeth := oauth2.SetAuthURLParam("code_challenge_method", "S256")
authChallengeVal := oauth2.SetAuthURLParam("code_challenge", challenge)
ln, err := net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
return nil, err
}
hostPort := ln.Addr().String()
_, port, err := net.SplitHostPort(hostPort)
if err != nil {
return nil, err
}
mux := http.NewServeMux()
srv := &http.Server{Handler: mux}
go func() {
err := srv.Serve(ln)
if err != http.ErrServerClosed {
fmt.Printf("Cannot start http server: %s", err)
os.Exit(1)
}
}()
myconf := conf
myconf.RedirectURL = fmt.Sprintf("http://127.0.0.1:%s/callback", port)
authUrl := myconf.AuthCodeURL(state, oauth2.AccessTypeOffline, authChallengeMeth, authChallengeVal)
authorizer := authorize{authUrl: authUrl}
mux.Handle("/authorize", authorizer)
callback := callback{state: state,
done: make(chan string, 1),
bad: make(chan bool, 1),
}
mux.Handle("/callback", callback)
return func() (string, error) {
var code string
var err error
fmt.Println("Authentication needed")
fmt.Println("Go to the following url in your browser:")
fmt.Printf("http://127.0.0.1:%s/authorize\n\n", port)
fmt.Println("Waiting for authentication response")
select {
case <-callback.bad:
err = fmt.Errorf("authentication did not complete successfully")
code = ""
case code = <-callback.done:
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer func() {
cancel()
}()
if stoperr := srv.Shutdown(ctx); stoperr != nil {
fmt.Printf("Server Shutdown Failed:%+v\n", stoperr)
}
return code, err
}, nil
}