1// Copyright 2011 Gary Burd
2//
3// Licensed under the Apache License, Version 2.0 (the "License"): you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15package main
16
17import (
18	"encoding/json"
19	"flag"
20	"fmt"
21	"io/ioutil"
22	"log"
23	"net/http"
24	"net/url"
25	"text/template"
26
27	"github.com/garyburd/go-oauth/examples/session"
28	"github.com/garyburd/go-oauth/oauth"
29)
30
31// Session state keys.
32var (
33	tempCredKey  = "tempCred"
34	tokenCredKey = "tokenCred"
35)
36
37var oauthClient = oauth.Client{
38	TemporaryCredentialRequestURI: "https://api.dropbox.com/1/oauth/request_token",
39	ResourceOwnerAuthorizationURI: "https://www.dropbox.com/1/oauth/authorize",
40	TokenRequestURI:               "https://api.dropbox.com/1/oauth/access_token",
41	SignatureMethod:               oauth.PLAINTEXT, // Dropbox also works with HMACSHA1
42}
43
44var credPath = flag.String("config", "config.json", "Path to configuration file containing the application's credentials.")
45
46func readCredentials() error {
47	b, err := ioutil.ReadFile(*credPath)
48	if err != nil {
49		return err
50	}
51	return json.Unmarshal(b, &oauthClient.Credentials)
52}
53
54// serveLogin gets the OAuth temp credentials and redirects the user to the
55// OAuth server's authorization page.
56func serveLogin(w http.ResponseWriter, r *http.Request) {
57	// Dropbox supports the older OAuth 1.0 specification where the callback URL
58	// is passed to the authorization endpoint.
59	callback := "http://" + r.Host + "/callback"
60	tempCred, err := oauthClient.RequestTemporaryCredentials(nil, "", nil)
61	if err != nil {
62		http.Error(w, "Error getting temp cred, "+err.Error(), 500)
63		return
64	}
65	s := session.Get(r)
66	s[tempCredKey] = tempCred
67	if err := session.Save(w, r, s); err != nil {
68		http.Error(w, "Error saving session , "+err.Error(), 500)
69		return
70	}
71	http.Redirect(w, r, oauthClient.AuthorizationURL(tempCred, url.Values{"oauth_callback": {callback}}), 302)
72}
73
74// serveOAuthCallback handles callbacks from the OAuth server.
75func serveOAuthCallback(w http.ResponseWriter, r *http.Request) {
76	s := session.Get(r)
77	tempCred, _ := s[tempCredKey].(*oauth.Credentials)
78	if tempCred == nil || tempCred.Token != r.FormValue("oauth_token") {
79		http.Error(w, "Unknown oauth_token.", 500)
80		return
81	}
82	tokenCred, _, err := oauthClient.RequestToken(nil, tempCred, r.FormValue("oauth_verifier"))
83	if err != nil {
84		http.Error(w, "Error getting request token, "+err.Error(), 500)
85		return
86	}
87	delete(s, tempCredKey)
88	s[tokenCredKey] = tokenCred
89	if err := session.Save(w, r, s); err != nil {
90		http.Error(w, "Error saving session , "+err.Error(), 500)
91		return
92	}
93	http.Redirect(w, r, "/", 302)
94}
95
96// serveLogout clears the authentication cookie.
97func serveLogout(w http.ResponseWriter, r *http.Request) {
98	s := session.Get(r)
99	delete(s, tokenCredKey)
100	if err := session.Save(w, r, s); err != nil {
101		http.Error(w, "Error saving session , "+err.Error(), 500)
102		return
103	}
104	http.Redirect(w, r, "/", 302)
105}
106
107// authHandler reads the auth cookie and invokes a handler with the result.
108type authHandler struct {
109	handler  func(w http.ResponseWriter, r *http.Request, c *oauth.Credentials)
110	optional bool
111}
112
113func (h *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
114	cred, _ := session.Get(r)[tokenCredKey].(*oauth.Credentials)
115	if cred == nil && !h.optional {
116		http.Error(w, "Not logged in.", 403)
117		return
118	}
119	h.handler(w, r, cred)
120}
121
122// respond responds to a request by executing the html template t with data.
123func respond(w http.ResponseWriter, t *template.Template, data interface{}) {
124	w.Header().Set("Content-Type", "text/html; charset=utf-8")
125	if err := t.Execute(w, data); err != nil {
126		log.Print(err)
127	}
128}
129
130func serveHome(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) {
131	if r.URL.Path != "/" {
132		http.NotFound(w, r)
133		return
134	}
135	if cred == nil {
136		respond(w, homeLoggedOutTmpl, nil)
137	} else {
138		respond(w, homeTmpl, nil)
139	}
140}
141
142func serveInfo(w http.ResponseWriter, r *http.Request, cred *oauth.Credentials) {
143	resp, err := oauthClient.Get(nil, cred, "https://api.dropbox.com/1/account/info", nil)
144	if err != nil {
145		http.Error(w, "Error getting info: "+err.Error(), 500)
146		return
147	}
148	defer resp.Body.Close()
149	b, err := ioutil.ReadAll(resp.Body)
150	if err != nil {
151		http.Error(w, "Error reading body:"+err.Error(), 500)
152		return
153	}
154	if resp.StatusCode != 200 {
155		http.Error(w, fmt.Sprintf("Get account/info returned status %d, %s", resp.StatusCode, b), 500)
156		return
157	}
158	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
159	w.Write(b)
160}
161
162var httpAddr = flag.String("addr", ":8080", "HTTP server address")
163
164func main() {
165	flag.Parse()
166	if err := readCredentials(); err != nil {
167		log.Fatalf("Error reading configuration, %v", err)
168	}
169	http.Handle("/", &authHandler{handler: serveHome, optional: true})
170	http.Handle("/info", &authHandler{handler: serveInfo})
171	http.HandleFunc("/login", serveLogin)
172	http.HandleFunc("/logout", serveLogout)
173	http.HandleFunc("/callback", serveOAuthCallback)
174	if err := http.ListenAndServe(*httpAddr, nil); err != nil {
175		log.Fatalf("Error listening, %v", err)
176	}
177}
178
179var (
180	homeLoggedOutTmpl = template.Must(template.New("loggedout").Parse(
181		`<html>
182<head>
183</head>
184<body>
185<a href="/login">login</a>
186</body>
187</html>`))
188
189	homeTmpl = template.Must(template.New("home").Parse(
190		`<html>
191<head>
192</head>
193<body>
194<p><a href="/info">info</a>
195<p><a href="/logout">logout</a>
196</body>
197</html>`))
198)
199