├── .gitignore ├── C# ├── .gitignore ├── README.md ├── othw.sln └── othw │ ├── App_Start │ ├── FilterConfig.cs │ ├── RouteConfig.cs │ └── WebApiConfig.cs │ ├── Controllers │ └── HomeController.cs │ ├── Global.asax │ ├── Global.asax.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Views │ └── Web.config │ ├── Web.Debug.config │ ├── Web.Release.config │ ├── Web.config │ ├── othw.csproj │ └── packages.config ├── Go ├── README.md └── app.go ├── Java ├── .gitignore ├── App.java ├── README.md └── pom.xml ├── JavaScript ├── README.md ├── cookie.min.js ├── index.html └── superagent.js ├── Node.js ├── .gitignore ├── README.md ├── app.js └── package.json ├── PHP ├── .gitignore ├── README.md ├── composer.json └── index.php ├── Perl ├── .gitignore ├── README.md └── app.pl ├── Python ├── .gitignore ├── README.md ├── app.py └── requirements.txt ├── README.md └── Ruby ├── .gitignore ├── Gemfile ├── README.md └── app.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /C#/.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | bin/ 4 | obj/ 5 | -------------------------------------------------------------------------------- /C#/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | * [ASP.NET MVC 4](http://www.asp.net/mvc) 4 | * [Visual Studio Express 2012 for Web](http://www.microsoft.com/visualstudio/eng/products/visual-studio-express-for-web) 5 | 6 | # Setup 7 | 8 | Enter your app key and secret in the code. (Look for `APP_KEY` and `APP_SECRET`.) 9 | 10 | # Running 11 | 12 | 1. Open `othw.sln` in Visual Studio. 13 | 2. Run the solution (`F5`). 14 | 15 | The browser should automatically open to http://localhost:5000. 16 | -------------------------------------------------------------------------------- /C#/othw.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Express 2012 for Web 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "othw", "othw\othw.csproj", "{F83DB53A-BE1E-4FA9-8B5E-E9D047125397}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {F83DB53A-BE1E-4FA9-8B5E-E9D047125397}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {F83DB53A-BE1E-4FA9-8B5E-E9D047125397}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {F83DB53A-BE1E-4FA9-8B5E-E9D047125397}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {F83DB53A-BE1E-4FA9-8B5E-E9D047125397}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /C#/othw/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | using System.Web.Mvc; 3 | 4 | namespace othw 5 | { 6 | public class FilterConfig 7 | { 8 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 9 | { 10 | filters.Add(new HandleErrorAttribute()); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /C#/othw/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Mvc; 6 | using System.Web.Routing; 7 | 8 | namespace othw 9 | { 10 | public class RouteConfig 11 | { 12 | public static void RegisterRoutes(RouteCollection routes) 13 | { 14 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 15 | 16 | routes.MapRoute( 17 | name: "Default", 18 | url: "{controller}/{action}/{id}", 19 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 20 | ); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /C#/othw/App_Start/WebApiConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web.Http; 5 | 6 | namespace othw 7 | { 8 | public static class WebApiConfig 9 | { 10 | public static void Register(HttpConfiguration config) 11 | { 12 | config.Routes.MapHttpRoute( 13 | name: "DefaultApi", 14 | routeTemplate: "api/{controller}/{id}", 15 | defaults: new { id = RouteParameter.Optional } 16 | ); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /C#/othw/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Web; 10 | using System.Web.Mvc; 11 | using System.Web.Helpers; 12 | 13 | namespace othw.Controllers 14 | { 15 | public class HomeController : Controller 16 | { 17 | private static string APP_KEY = ""; 18 | private static string APP_SECRET = ""; 19 | 20 | private static string GenerateCsrfToken() 21 | { 22 | var bytes = new byte[18]; 23 | new RNGCryptoServiceProvider().GetBytes(bytes); 24 | return Convert.ToBase64String(bytes).Replace("+", "-").Replace("/", "_"); 25 | } 26 | 27 | public ActionResult Index() 28 | { 29 | var token = GenerateCsrfToken(); 30 | Response.SetCookie(new HttpCookie("csrf") { Value = token }); 31 | return Redirect(string.Format( 32 | "https://www.dropbox.com/1/oauth2/authorize?client_id={0}&response_type=code&state={1}&redirect_uri={2}", 33 | APP_KEY, 34 | token, 35 | Uri.EscapeDataString(Url.Action("Callback", null, null, Request.Url.Scheme)) 36 | )); 37 | } 38 | 39 | public async Task Callback() 40 | { 41 | string csrf = null; 42 | var cookie = Request.Cookies["csrf"]; 43 | if (cookie != null) 44 | { 45 | csrf = cookie.Value; 46 | } 47 | Response.Cookies.Set(new HttpCookie("csrf") { Expires = DateTime.UtcNow.AddDays(-1) }); 48 | 49 | if (csrf != Request.QueryString["state"]) 50 | { 51 | return new HttpUnauthorizedResult("Potential CSRF attack."); 52 | } 53 | 54 | var code = Request.QueryString["code"]; 55 | 56 | var client = new HttpClient() 57 | { 58 | BaseAddress = new Uri("https://api.dropbox.com"), 59 | }; 60 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(APP_KEY + ":" + APP_SECRET))); 61 | 62 | var response = await client.PostAsync("/1/oauth2/token", 63 | new FormUrlEncodedContent(new List> { 64 | new KeyValuePair("code", Request.QueryString["code"]), 65 | new KeyValuePair("grant_type", "authorization_code"), 66 | new KeyValuePair("redirect_uri", Url.Action("Callback", null, null, Request.Url.Scheme)) 67 | })); 68 | var json = System.Web.Helpers.Json.Decode(await response.Content.ReadAsStringAsync()); 69 | var token = json.access_token; 70 | 71 | client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); 72 | 73 | response = await client.GetAsync("/1/account/info"); 74 | json = System.Web.Helpers.Json.Decode(await response.Content.ReadAsStringAsync()); 75 | return Content(string.Format("Successfully logged in as {0}.", json.display_name)); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /C#/othw/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="othw.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /C#/othw/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Web; 5 | using System.Web.Http; 6 | using System.Web.Mvc; 7 | using System.Web.Routing; 8 | 9 | namespace othw 10 | { 11 | // Note: For instructions on enabling IIS6 or IIS7 classic mode, 12 | // visit http://go.microsoft.com/?LinkId=9394801 13 | public class MvcApplication : System.Web.HttpApplication 14 | { 15 | protected void Application_Start() 16 | { 17 | AreaRegistration.RegisterAllAreas(); 18 | 19 | WebApiConfig.Register(GlobalConfiguration.Configuration); 20 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 21 | RouteConfig.RegisterRoutes(RouteTable.Routes); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /C#/othw/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("othw")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("othw")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e17a8033-2189-4d8c-8a55-f0c2a193c951")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /C#/othw/Views/Web.config: -------------------------------------------------------------------------------- 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 | 39 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /C#/othw/Web.Debug.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /C#/othw/Web.Release.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /C#/othw/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 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 | -------------------------------------------------------------------------------- /C#/othw/othw.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {F83DB53A-BE1E-4FA9-8B5E-E9D047125397} 11 | {E3E379DF-F4C6-4180-9B81-6769533ABE47};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | othw 15 | othw 16 | v4.5 17 | false 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\ 29 | DEBUG;TRACE 30 | prompt 31 | 4 32 | 33 | 34 | pdbonly 35 | true 36 | bin\ 37 | TRACE 38 | prompt 39 | 4 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | True 63 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 64 | 65 | 66 | True 67 | ..\packages\Microsoft.AspNet.Mvc.FixedDisplayModes.1.0.0\lib\net40\Microsoft.Web.Mvc.FixedDisplayModes.dll 68 | 69 | 70 | ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll 71 | 72 | 73 | 74 | 75 | ..\packages\Microsoft.AspNet.WebApi.Client.4.0.20710.0\lib\net40\System.Net.Http.Formatting.dll 76 | 77 | 78 | 79 | 80 | True 81 | ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.Helpers.dll 82 | 83 | 84 | ..\packages\Microsoft.AspNet.WebApi.Core.4.0.20710.0\lib\net40\System.Web.Http.dll 85 | 86 | 87 | ..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.20710.0\lib\net40\System.Web.Http.WebHost.dll 88 | 89 | 90 | True 91 | ..\packages\Microsoft.AspNet.Mvc.4.0.20710.0\lib\net40\System.Web.Mvc.dll 92 | 93 | 94 | True 95 | ..\packages\Microsoft.AspNet.Razor.2.0.20715.0\lib\net40\System.Web.Razor.dll 96 | 97 | 98 | True 99 | ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.dll 100 | 101 | 102 | True 103 | ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Deployment.dll 104 | 105 | 106 | True 107 | ..\packages\Microsoft.AspNet.WebPages.2.0.20710.0\lib\net40\System.Web.WebPages.Razor.dll 108 | 109 | 110 | 111 | 112 | 113 | Global.asax 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Web.config 125 | 126 | 127 | Web.config 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 10.0 140 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | True 153 | True 154 | 49862 155 | / 156 | http://localhost:5000/ 157 | False 158 | False 159 | 160 | 161 | False 162 | 163 | 164 | 165 | 166 | 172 | -------------------------------------------------------------------------------- /C#/othw/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Go/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | * [Go 1.1+](http://golang.org) 4 | 5 | # Setup 6 | 7 | Enter your app key and secret in the code. (Look for `APP_KEY` and `APP_SECRET`.) 8 | 9 | # Running 10 | 11 | `go run app.go` 12 | 13 | Open the browser to http://127.0.0.1:5000. 14 | -------------------------------------------------------------------------------- /Go/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | "encoding/base64" 8 | "crypto/rand" 9 | "encoding/json" 10 | ) 11 | 12 | const APP_KEY = "" 13 | const APP_SECRET = "" 14 | 15 | func getCallbackURL(r *http.Request) string { 16 | scheme := "http" 17 | forwarded := r.Header.Get("X-Forwarded-Proto") 18 | if len(forwarded) > 0 { 19 | scheme = forwarded 20 | } 21 | return (&url.URL{ 22 | Scheme: scheme, 23 | Host: r.Host, 24 | Path: "/callback", 25 | }).String() 26 | } 27 | 28 | func index(w http.ResponseWriter, r *http.Request) { 29 | if r.URL.Path != "/" { 30 | http.NotFound(w, r) 31 | return 32 | } 33 | 34 | b := make([]byte, 18) 35 | rand.Read(b) 36 | csrf := base64.StdEncoding.EncodeToString(b) 37 | http.SetCookie(w, &http.Cookie{Name: "csrf", Value: csrf}) 38 | 39 | http.Redirect(w, r, "https://www.dropbox.com/1/oauth2/authorize?" + 40 | url.Values{ 41 | "client_id": {APP_KEY}, 42 | "redirect_uri": {getCallbackURL(r)}, 43 | "response_type": {"code"}, 44 | "state": {csrf}, 45 | }.Encode(), 302) 46 | } 47 | 48 | func decodeResponse(r *http.Response, m interface{}) { 49 | defer r.Body.Close() 50 | json.NewDecoder(r.Body).Decode(m) 51 | } 52 | 53 | func callback(w http.ResponseWriter, r *http.Request) { 54 | http.SetCookie(w, &http.Cookie{Name: "csrf", MaxAge: -1}) 55 | state := r.FormValue("state"); 56 | cookie, _ := r.Cookie("csrf") 57 | if cookie == nil || cookie.Value != state { 58 | http.Error(w, "Possible CSRF attack.", http.StatusUnauthorized); 59 | return 60 | } 61 | 62 | resp, err := http.PostForm(fmt.Sprintf("https://%s:%s@api.dropbox.com/1/oauth2/token", APP_KEY, APP_SECRET), 63 | url.Values{ 64 | "redirect_uri": {getCallbackURL(r)}, 65 | "code": {r.FormValue("code")}, 66 | "grant_type": {"authorization_code"}, 67 | }) 68 | if err != nil { 69 | panic(err); 70 | } 71 | tokenMessage := struct { Access_token string }{} 72 | decodeResponse(resp, &tokenMessage) 73 | token := tokenMessage.Access_token 74 | 75 | req, _ := http.NewRequest("GET", "https://api.dropbox.com/1/account/info", nil) 76 | req.Header.Set("Authorization", "Bearer " + token) 77 | resp, err = http.DefaultClient.Do(req) 78 | if err != nil { 79 | panic(err) 80 | } 81 | nameMessage := struct { Display_name string }{} 82 | decodeResponse(resp, &nameMessage) 83 | 84 | fmt.Fprintf(w, "Successfully authenticated as %s.", nameMessage.Display_name) 85 | } 86 | 87 | func main() { 88 | http.HandleFunc("/callback", callback) 89 | http.HandleFunc("/", index) 90 | http.ListenAndServe(":5000", nil) 91 | } 92 | -------------------------------------------------------------------------------- /Java/.gitignore: -------------------------------------------------------------------------------- 1 | libs/ 2 | *.class 3 | -------------------------------------------------------------------------------- /Java/App.java: -------------------------------------------------------------------------------- 1 | import java.util.Map; 2 | import java.net.URISyntaxException; 3 | import java.security.SecureRandom; 4 | import org.apache.commons.io.*; 5 | import org.apache.commons.codec.binary.Base64; 6 | import org.apache.http.client.fluent.Form; 7 | import org.apache.http.client.fluent.Request; 8 | import org.apache.http.client.utils.URIBuilder; 9 | import org.json.simple.*; 10 | import static spark.Spark.*; 11 | import spark.*; 12 | 13 | public class App { 14 | static String APP_KEY = ""; 15 | static String APP_SECRET = ""; 16 | 17 | // Generate a random string to use as a CSRF token. 18 | public static String generateCSRFToken() { 19 | byte[] b = new byte[18]; 20 | new SecureRandom().nextBytes(b); 21 | return Base64.encodeBase64URLSafeString(b); 22 | } 23 | 24 | public static String getRedirectURI(spark.Request request) throws URISyntaxException { 25 | return new URIBuilder(request.url()).setPath("/callback").build().toString(); 26 | } 27 | 28 | public static void main(String[] args) throws Exception { 29 | setPort(5000); 30 | 31 | // Main route when the app is initially loaded. 32 | get(new Route("/") { 33 | @Override 34 | public Object handle(spark.Request request, Response response) { 35 | String csrfToken = generateCSRFToken(); 36 | 37 | // Store the CSRF token in a cookie, to be checked in the callback. 38 | response.cookie("csrf", csrfToken); 39 | 40 | try { 41 | // Redirect the user to authorize the app with Dropbox. 42 | response.redirect(new URIBuilder("https://www.dropbox.com/1/oauth2/authorize") 43 | .addParameter("client_id", APP_KEY) 44 | .addParameter("response_type", "code") 45 | .addParameter("redirect_uri", getRedirectURI(request)) 46 | .addParameter("state", csrfToken) 47 | .build().toString()); 48 | 49 | return null; 50 | } catch (Exception e) { 51 | return "ERROR: " + e.toString(); 52 | } 53 | } 54 | }); 55 | 56 | // Route for when the user is redirected back to our app. 57 | get(new Route("/callback") { 58 | @Override 59 | public Object handle(spark.Request request, Response response) { 60 | // The CSRF token will only be used once. 61 | response.removeCookie("csrf"); 62 | 63 | // If the CSRF token doesn't match, raise an error. 64 | if (!request.cookie("csrf").equals(request.queryParams("state"))) { 65 | halt(401, "Potential CSRF attack."); 66 | } 67 | 68 | // This is the authorization code from the OAuth flow. 69 | String code = request.queryParams("code"); 70 | 71 | try { 72 | // Exchange the authorization code for an access token. 73 | Map json = (Map) JSONValue.parse( 74 | Request.Post("https://api.dropbox.com/1/oauth2/token") 75 | .bodyForm(Form.form() 76 | .add("code", code) 77 | .add("grant_type", "authorization_code") 78 | .add("redirect_uri", getRedirectURI(request)) 79 | .build()) 80 | .addHeader("Authorization", "Basic " + Base64.encodeBase64String((APP_KEY+":"+APP_SECRET).getBytes())) 81 | .execute().returnContent().asString()); 82 | String accessToken = (String) json.get("access_token"); 83 | 84 | // Call the /account/info API with the access token. 85 | json = (Map) JSONValue.parse( 86 | Request.Get("https://api.dropbox.com/1/account/info") 87 | .addHeader("Authorization", "Bearer " + accessToken) 88 | .execute().returnContent().asString()); 89 | 90 | return String.format("Successfully authenticated as %s.", json.get("display_name")); 91 | } catch (Exception e) { 92 | return "ERROR: " + e.toString(); 93 | } 94 | } 95 | }); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Java/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | * [Java 1.7+](http://www.oracle.com/technetwork/java/javase/downloads/index.html) 4 | * [Maven](http://maven.apache.org/) 5 | 6 | # Setup 7 | 8 | 1. Enter your app key and secret in the code. (Look for `APP_KEY` and `APP_SECRET`.) 9 | 2. Install dependencies: `mvn` 10 | 11 | # Running 12 | 13 | 1. Compile: `javac -cp "libs/*" App.java 14 | 2. Run: `java -cp "libs/*:." App` 15 | 16 | Open the browser to http://127.0.0.1:5000. 17 | -------------------------------------------------------------------------------- /Java/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | com.smarx 7 | oauth2-hardway 8 | 1.0 9 | 10 | jar 11 | 12 | 13 | libs 14 | 15 | 16 | 17 | 18 | com.googlecode.json-simple 19 | json-simple 20 | 1.1.1 21 | 22 | 23 | com.sparkjava 24 | spark-core 25 | 1.0 26 | 27 | 28 | commons-codec 29 | commons-codec 30 | 1.8 31 | 32 | 33 | org.apache.commons 34 | commons-io 35 | 1.3.2 36 | 37 | 38 | org.apache.httpcomponents 39 | fluent-hc 40 | 4.2.5 41 | 42 | 43 | 44 | dependency:copy-dependencies 45 | 46 | 47 | 48 | ${libraries.folder} 49 | false 50 | false 51 | true 52 | 53 | org.apache.maven.plugins 54 | maven-dependency-plugin 55 | 2.7 56 | 57 | 58 | copy-dependencies 59 | package 60 | 61 | copy-dependencies 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /JavaScript/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | * A web server 4 | * A web browser 5 | 6 | # Setup 7 | 8 | Enter your app key in the code. (Look for `APP_KEY`.) 9 | 10 | # Running 11 | 12 | Host `index.html` under a web server (e.g. `python -m SimpleHTTPServer 5000`), and then open the page in the browser. 13 | -------------------------------------------------------------------------------- /JavaScript/cookie.min.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012 Florian H., https://github.com/js-coder https://github.com/js-coder/cookie.js 2 | !function(e,t){var n=function(){return n.get.apply(n,arguments)},r=n.utils={isArray:Array.isArray||function(e){return Object.prototype.toString.call(e)==="[object Array]"},isPlainObject:function(e){return!!e&&Object.prototype.toString.call(e)==="[object Object]"},toArray:function(e){return Array.prototype.slice.call(e)},getKeys:Object.keys||function(e){var t=[],n="";for(n in e)e.hasOwnProperty(n)&&t.push(n);return t},escape:function(e){return String(e).replace(/[,;"\\=\s%]/g,function(e){return encodeURIComponent(e)})},retrieve:function(e,t){return e==null?t:e}};n.defaults={},n.expiresMultiplier=86400,n.set=function(n,i,s){if(r.isPlainObject(n))for(var o in n)n.hasOwnProperty(o)&&this.set(o,n[o],i);else{s=r.isPlainObject(s)?s:{expires:s};var u=s.expires!==t?s.expires:this.defaults.expires||"",a=typeof u;a==="string"&&u!==""?u=new Date(u):a==="number"&&(u=new Date(+(new Date)+1e3*this.expiresMultiplier*u)),u!==""&&"toGMTString"in u&&(u=";expires="+u.toGMTString());var f=s.path||this.defaults.path;f=f?";path="+f:"";var l=s.domain||this.defaults.domain;l=l?";domain="+l:"";var c=s.secure||this.defaults.secure?";secure":"";e.cookie=r.escape(n)+"="+r.escape(i)+u+f+l+c}return this},n.remove=function(e){e=r.isArray(e)?e:r.toArray(arguments);for(var t=0,n=e.length;t 2 | 3 | 4 | 5 | 6 | 7 | 8 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /JavaScript/superagent.js: -------------------------------------------------------------------------------- 1 | ;(function(){ 2 | 3 | /** 4 | * Require the given path. 5 | * 6 | * @param {String} path 7 | * @return {Object} exports 8 | * @api public 9 | */ 10 | 11 | function require(path, parent, orig) { 12 | var resolved = require.resolve(path); 13 | 14 | // lookup failed 15 | if (null == resolved) { 16 | orig = orig || path; 17 | parent = parent || 'root'; 18 | var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); 19 | err.path = orig; 20 | err.parent = parent; 21 | err.require = true; 22 | throw err; 23 | } 24 | 25 | var module = require.modules[resolved]; 26 | 27 | // perform real require() 28 | // by invoking the module's 29 | // registered function 30 | if (!module.exports) { 31 | module.exports = {}; 32 | module.client = module.component = true; 33 | module.call(this, module.exports, require.relative(resolved), module); 34 | } 35 | 36 | return module.exports; 37 | } 38 | 39 | /** 40 | * Registered modules. 41 | */ 42 | 43 | require.modules = {}; 44 | 45 | /** 46 | * Registered aliases. 47 | */ 48 | 49 | require.aliases = {}; 50 | 51 | /** 52 | * Resolve `path`. 53 | * 54 | * Lookup: 55 | * 56 | * - PATH/index.js 57 | * - PATH.js 58 | * - PATH 59 | * 60 | * @param {String} path 61 | * @return {String} path or null 62 | * @api private 63 | */ 64 | 65 | require.resolve = function(path) { 66 | if (path.charAt(0) === '/') path = path.slice(1); 67 | var index = path + '/index.js'; 68 | 69 | var paths = [ 70 | path, 71 | path + '.js', 72 | path + '.json', 73 | path + '/index.js', 74 | path + '/index.json' 75 | ]; 76 | 77 | for (var i = 0; i < paths.length; i++) { 78 | var path = paths[i]; 79 | if (require.modules.hasOwnProperty(path)) return path; 80 | } 81 | 82 | if (require.aliases.hasOwnProperty(index)) { 83 | return require.aliases[index]; 84 | } 85 | }; 86 | 87 | /** 88 | * Normalize `path` relative to the current path. 89 | * 90 | * @param {String} curr 91 | * @param {String} path 92 | * @return {String} 93 | * @api private 94 | */ 95 | 96 | require.normalize = function(curr, path) { 97 | var segs = []; 98 | 99 | if ('.' != path.charAt(0)) return path; 100 | 101 | curr = curr.split('/'); 102 | path = path.split('/'); 103 | 104 | for (var i = 0; i < path.length; ++i) { 105 | if ('..' == path[i]) { 106 | curr.pop(); 107 | } else if ('.' != path[i] && '' != path[i]) { 108 | segs.push(path[i]); 109 | } 110 | } 111 | 112 | return curr.concat(segs).join('/'); 113 | }; 114 | 115 | /** 116 | * Register module at `path` with callback `definition`. 117 | * 118 | * @param {String} path 119 | * @param {Function} definition 120 | * @api private 121 | */ 122 | 123 | require.register = function(path, definition) { 124 | require.modules[path] = definition; 125 | }; 126 | 127 | /** 128 | * Alias a module definition. 129 | * 130 | * @param {String} from 131 | * @param {String} to 132 | * @api private 133 | */ 134 | 135 | require.alias = function(from, to) { 136 | if (!require.modules.hasOwnProperty(from)) { 137 | throw new Error('Failed to alias "' + from + '", it does not exist'); 138 | } 139 | require.aliases[to] = from; 140 | }; 141 | 142 | /** 143 | * Return a require function relative to the `parent` path. 144 | * 145 | * @param {String} parent 146 | * @return {Function} 147 | * @api private 148 | */ 149 | 150 | require.relative = function(parent) { 151 | var p = require.normalize(parent, '..'); 152 | 153 | /** 154 | * lastIndexOf helper. 155 | */ 156 | 157 | function lastIndexOf(arr, obj) { 158 | var i = arr.length; 159 | while (i--) { 160 | if (arr[i] === obj) return i; 161 | } 162 | return -1; 163 | } 164 | 165 | /** 166 | * The relative require() itself. 167 | */ 168 | 169 | function localRequire(path) { 170 | var resolved = localRequire.resolve(path); 171 | return require(resolved, parent, path); 172 | } 173 | 174 | /** 175 | * Resolve relative to the parent. 176 | */ 177 | 178 | localRequire.resolve = function(path) { 179 | var c = path.charAt(0); 180 | if ('/' == c) return path.slice(1); 181 | if ('.' == c) return require.normalize(p, path); 182 | 183 | // resolve deps by returning 184 | // the dep in the nearest "deps" 185 | // directory 186 | var segs = parent.split('/'); 187 | var i = lastIndexOf(segs, 'deps') + 1; 188 | if (!i) i = 0; 189 | path = segs.slice(0, i + 1).join('/') + '/deps/' + path; 190 | return path; 191 | }; 192 | 193 | /** 194 | * Check if module is defined at `path`. 195 | */ 196 | 197 | localRequire.exists = function(path) { 198 | return require.modules.hasOwnProperty(localRequire.resolve(path)); 199 | }; 200 | 201 | return localRequire; 202 | }; 203 | require.register("component-indexof/index.js", function(exports, require, module){ 204 | 205 | var indexOf = [].indexOf; 206 | 207 | module.exports = function(arr, obj){ 208 | if (indexOf) return arr.indexOf(obj); 209 | for (var i = 0; i < arr.length; ++i) { 210 | if (arr[i] === obj) return i; 211 | } 212 | return -1; 213 | }; 214 | }); 215 | require.register("component-emitter/index.js", function(exports, require, module){ 216 | 217 | /** 218 | * Module dependencies. 219 | */ 220 | 221 | var index = require('indexof'); 222 | 223 | /** 224 | * Expose `Emitter`. 225 | */ 226 | 227 | module.exports = Emitter; 228 | 229 | /** 230 | * Initialize a new `Emitter`. 231 | * 232 | * @api public 233 | */ 234 | 235 | function Emitter(obj) { 236 | if (obj) return mixin(obj); 237 | }; 238 | 239 | /** 240 | * Mixin the emitter properties. 241 | * 242 | * @param {Object} obj 243 | * @return {Object} 244 | * @api private 245 | */ 246 | 247 | function mixin(obj) { 248 | for (var key in Emitter.prototype) { 249 | obj[key] = Emitter.prototype[key]; 250 | } 251 | return obj; 252 | } 253 | 254 | /** 255 | * Listen on the given `event` with `fn`. 256 | * 257 | * @param {String} event 258 | * @param {Function} fn 259 | * @return {Emitter} 260 | * @api public 261 | */ 262 | 263 | Emitter.prototype.on = function(event, fn){ 264 | this._callbacks = this._callbacks || {}; 265 | (this._callbacks[event] = this._callbacks[event] || []) 266 | .push(fn); 267 | return this; 268 | }; 269 | 270 | /** 271 | * Adds an `event` listener that will be invoked a single 272 | * time then automatically removed. 273 | * 274 | * @param {String} event 275 | * @param {Function} fn 276 | * @return {Emitter} 277 | * @api public 278 | */ 279 | 280 | Emitter.prototype.once = function(event, fn){ 281 | var self = this; 282 | this._callbacks = this._callbacks || {}; 283 | 284 | function on() { 285 | self.off(event, on); 286 | fn.apply(this, arguments); 287 | } 288 | 289 | fn._off = on; 290 | this.on(event, on); 291 | return this; 292 | }; 293 | 294 | /** 295 | * Remove the given callback for `event` or all 296 | * registered callbacks. 297 | * 298 | * @param {String} event 299 | * @param {Function} fn 300 | * @return {Emitter} 301 | * @api public 302 | */ 303 | 304 | Emitter.prototype.off = 305 | Emitter.prototype.removeListener = 306 | Emitter.prototype.removeAllListeners = function(event, fn){ 307 | this._callbacks = this._callbacks || {}; 308 | 309 | // all 310 | if (0 == arguments.length) { 311 | this._callbacks = {}; 312 | return this; 313 | } 314 | 315 | // specific event 316 | var callbacks = this._callbacks[event]; 317 | if (!callbacks) return this; 318 | 319 | // remove all handlers 320 | if (1 == arguments.length) { 321 | delete this._callbacks[event]; 322 | return this; 323 | } 324 | 325 | // remove specific handler 326 | var i = index(callbacks, fn._off || fn); 327 | if (~i) callbacks.splice(i, 1); 328 | return this; 329 | }; 330 | 331 | /** 332 | * Emit `event` with the given args. 333 | * 334 | * @param {String} event 335 | * @param {Mixed} ... 336 | * @return {Emitter} 337 | */ 338 | 339 | Emitter.prototype.emit = function(event){ 340 | this._callbacks = this._callbacks || {}; 341 | var args = [].slice.call(arguments, 1) 342 | , callbacks = this._callbacks[event]; 343 | 344 | if (callbacks) { 345 | callbacks = callbacks.slice(0); 346 | for (var i = 0, len = callbacks.length; i < len; ++i) { 347 | callbacks[i].apply(this, args); 348 | } 349 | } 350 | 351 | return this; 352 | }; 353 | 354 | /** 355 | * Return array of callbacks for `event`. 356 | * 357 | * @param {String} event 358 | * @return {Array} 359 | * @api public 360 | */ 361 | 362 | Emitter.prototype.listeners = function(event){ 363 | this._callbacks = this._callbacks || {}; 364 | return this._callbacks[event] || []; 365 | }; 366 | 367 | /** 368 | * Check if this emitter has `event` handlers. 369 | * 370 | * @param {String} event 371 | * @return {Boolean} 372 | * @api public 373 | */ 374 | 375 | Emitter.prototype.hasListeners = function(event){ 376 | return !! this.listeners(event).length; 377 | }; 378 | 379 | }); 380 | require.register("RedVentures-reduce/index.js", function(exports, require, module){ 381 | 382 | /** 383 | * Reduce `arr` with `fn`. 384 | * 385 | * @param {Array} arr 386 | * @param {Function} fn 387 | * @param {Mixed} initial 388 | * 389 | * TODO: combatible error handling? 390 | */ 391 | 392 | module.exports = function(arr, fn, initial){ 393 | var idx = 0; 394 | var len = arr.length; 395 | var curr = arguments.length == 3 396 | ? initial 397 | : arr[idx++]; 398 | 399 | while (idx < len) { 400 | curr = fn.call(null, curr, arr[idx], ++idx, arr); 401 | } 402 | 403 | return curr; 404 | }; 405 | }); 406 | require.register("superagent/lib/client.js", function(exports, require, module){ 407 | 408 | /** 409 | * Module dependencies. 410 | */ 411 | 412 | var Emitter = require('emitter'); 413 | var reduce = require('reduce'); 414 | 415 | /** 416 | * Root reference for iframes. 417 | */ 418 | 419 | var root = 'undefined' == typeof window 420 | ? this 421 | : window; 422 | 423 | /** 424 | * Noop. 425 | */ 426 | 427 | function noop(){}; 428 | 429 | /** 430 | * Check if `obj` is a host object, 431 | * we don't want to serialize these :) 432 | * 433 | * TODO: future proof, move to compoent land 434 | * 435 | * @param {Object} obj 436 | * @return {Boolean} 437 | * @api private 438 | */ 439 | 440 | function isHost(obj) { 441 | var str = {}.toString.call(obj); 442 | 443 | switch (str) { 444 | case '[object File]': 445 | case '[object Blob]': 446 | case '[object FormData]': 447 | return true; 448 | default: 449 | return false; 450 | } 451 | } 452 | 453 | /** 454 | * Determine XHR. 455 | */ 456 | 457 | function getXHR() { 458 | if (root.XMLHttpRequest 459 | && ('file:' != root.location.protocol || !root.ActiveXObject)) { 460 | return new XMLHttpRequest; 461 | } else { 462 | try { return new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {} 463 | try { return new ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch(e) {} 464 | try { return new ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch(e) {} 465 | try { return new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {} 466 | } 467 | return false; 468 | } 469 | 470 | /** 471 | * Removes leading and trailing whitespace, added to support IE. 472 | * 473 | * @param {String} s 474 | * @return {String} 475 | * @api private 476 | */ 477 | 478 | var trim = ''.trim 479 | ? function(s) { return s.trim(); } 480 | : function(s) { return s.replace(/(^\s*|\s*$)/g, ''); }; 481 | 482 | /** 483 | * Check if `obj` is an object. 484 | * 485 | * @param {Object} obj 486 | * @return {Boolean} 487 | * @api private 488 | */ 489 | 490 | function isObject(obj) { 491 | return obj === Object(obj); 492 | } 493 | 494 | /** 495 | * Serialize the given `obj`. 496 | * 497 | * @param {Object} obj 498 | * @return {String} 499 | * @api private 500 | */ 501 | 502 | function serialize(obj) { 503 | if (!isObject(obj)) return obj; 504 | var pairs = []; 505 | for (var key in obj) { 506 | pairs.push(encodeURIComponent(key) 507 | + '=' + encodeURIComponent(obj[key])); 508 | } 509 | return pairs.join('&'); 510 | } 511 | 512 | /** 513 | * Expose serialization method. 514 | */ 515 | 516 | request.serializeObject = serialize; 517 | 518 | /** 519 | * Parse the given x-www-form-urlencoded `str`. 520 | * 521 | * @param {String} str 522 | * @return {Object} 523 | * @api private 524 | */ 525 | 526 | function parseString(str) { 527 | var obj = {}; 528 | var pairs = str.split('&'); 529 | var parts; 530 | var pair; 531 | 532 | for (var i = 0, len = pairs.length; i < len; ++i) { 533 | pair = pairs[i]; 534 | parts = pair.split('='); 535 | obj[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); 536 | } 537 | 538 | return obj; 539 | } 540 | 541 | /** 542 | * Expose parser. 543 | */ 544 | 545 | request.parseString = parseString; 546 | 547 | /** 548 | * Default MIME type map. 549 | * 550 | * superagent.types.xml = 'application/xml'; 551 | * 552 | */ 553 | 554 | request.types = { 555 | html: 'text/html', 556 | json: 'application/json', 557 | urlencoded: 'application/x-www-form-urlencoded', 558 | 'form': 'application/x-www-form-urlencoded', 559 | 'form-data': 'application/x-www-form-urlencoded' 560 | }; 561 | 562 | /** 563 | * Default serialization map. 564 | * 565 | * superagent.serialize['application/xml'] = function(obj){ 566 | * return 'generated xml here'; 567 | * }; 568 | * 569 | */ 570 | 571 | request.serialize = { 572 | 'application/x-www-form-urlencoded': serialize, 573 | 'application/json': JSON.stringify 574 | }; 575 | 576 | /** 577 | * Default parsers. 578 | * 579 | * superagent.parse['application/xml'] = function(str){ 580 | * return { object parsed from str }; 581 | * }; 582 | * 583 | */ 584 | 585 | request.parse = { 586 | 'application/x-www-form-urlencoded': parseString, 587 | 'application/json': JSON.parse 588 | }; 589 | 590 | /** 591 | * Parse the given header `str` into 592 | * an object containing the mapped fields. 593 | * 594 | * @param {String} str 595 | * @return {Object} 596 | * @api private 597 | */ 598 | 599 | function parseHeader(str) { 600 | var lines = str.split(/\r?\n/); 601 | var fields = {}; 602 | var index; 603 | var line; 604 | var field; 605 | var val; 606 | 607 | lines.pop(); // trailing CRLF 608 | 609 | for (var i = 0, len = lines.length; i < len; ++i) { 610 | line = lines[i]; 611 | index = line.indexOf(':'); 612 | field = line.slice(0, index).toLowerCase(); 613 | val = trim(line.slice(index + 1)); 614 | fields[field] = val; 615 | } 616 | 617 | return fields; 618 | } 619 | 620 | /** 621 | * Return the mime type for the given `str`. 622 | * 623 | * @param {String} str 624 | * @return {String} 625 | * @api private 626 | */ 627 | 628 | function type(str){ 629 | return str.split(/ *; */).shift(); 630 | }; 631 | 632 | /** 633 | * Return header field parameters. 634 | * 635 | * @param {String} str 636 | * @return {Object} 637 | * @api private 638 | */ 639 | 640 | function params(str){ 641 | return reduce(str.split(/ *; */), function(obj, str){ 642 | var parts = str.split(/ *= */) 643 | , key = parts.shift() 644 | , val = parts.shift(); 645 | 646 | if (key && val) obj[key] = val; 647 | return obj; 648 | }, {}); 649 | }; 650 | 651 | /** 652 | * Initialize a new `Response` with the given `xhr`. 653 | * 654 | * - set flags (.ok, .error, etc) 655 | * - parse header 656 | * 657 | * Examples: 658 | * 659 | * Aliasing `superagent` as `request` is nice: 660 | * 661 | * request = superagent; 662 | * 663 | * We can use the promise-like API, or pass callbacks: 664 | * 665 | * request.get('/').end(function(res){}); 666 | * request.get('/', function(res){}); 667 | * 668 | * Sending data can be chained: 669 | * 670 | * request 671 | * .post('/user') 672 | * .send({ name: 'tj' }) 673 | * .end(function(res){}); 674 | * 675 | * Or passed to `.send()`: 676 | * 677 | * request 678 | * .post('/user') 679 | * .send({ name: 'tj' }, function(res){}); 680 | * 681 | * Or passed to `.post()`: 682 | * 683 | * request 684 | * .post('/user', { name: 'tj' }) 685 | * .end(function(res){}); 686 | * 687 | * Or further reduced to a single call for simple cases: 688 | * 689 | * request 690 | * .post('/user', { name: 'tj' }, function(res){}); 691 | * 692 | * @param {XMLHTTPRequest} xhr 693 | * @param {Object} options 694 | * @api private 695 | */ 696 | 697 | function Response(xhr, options) { 698 | options = options || {}; 699 | this.xhr = xhr; 700 | this.text = xhr.responseText; 701 | this.setStatusProperties(xhr.status); 702 | this.header = this.headers = parseHeader(xhr.getAllResponseHeaders()); 703 | // getAllResponseHeaders sometimes falsely returns "" for CORS requests, but 704 | // getResponseHeader still works. so we get content-type even if getting 705 | // other headers fails. 706 | this.header['content-type'] = xhr.getResponseHeader('content-type'); 707 | this.setHeaderProperties(this.header); 708 | this.body = this.parseBody(this.text); 709 | } 710 | 711 | /** 712 | * Get case-insensitive `field` value. 713 | * 714 | * @param {String} field 715 | * @return {String} 716 | * @api public 717 | */ 718 | 719 | Response.prototype.get = function(field){ 720 | return this.header[field.toLowerCase()]; 721 | }; 722 | 723 | /** 724 | * Set header related properties: 725 | * 726 | * - `.type` the content type without params 727 | * 728 | * A response of "Content-Type: text/plain; charset=utf-8" 729 | * will provide you with a `.type` of "text/plain". 730 | * 731 | * @param {Object} header 732 | * @api private 733 | */ 734 | 735 | Response.prototype.setHeaderProperties = function(header){ 736 | // content-type 737 | var ct = this.header['content-type'] || ''; 738 | this.type = type(ct); 739 | 740 | // params 741 | var obj = params(ct); 742 | for (var key in obj) this[key] = obj[key]; 743 | }; 744 | 745 | /** 746 | * Parse the given body `str`. 747 | * 748 | * Used for auto-parsing of bodies. Parsers 749 | * are defined on the `superagent.parse` object. 750 | * 751 | * @param {String} str 752 | * @return {Mixed} 753 | * @api private 754 | */ 755 | 756 | Response.prototype.parseBody = function(str){ 757 | var parse = request.parse[this.type]; 758 | return parse 759 | ? parse(str) 760 | : null; 761 | }; 762 | 763 | /** 764 | * Set flags such as `.ok` based on `status`. 765 | * 766 | * For example a 2xx response will give you a `.ok` of __true__ 767 | * whereas 5xx will be __false__ and `.error` will be __true__. The 768 | * `.clientError` and `.serverError` are also available to be more 769 | * specific, and `.statusType` is the class of error ranging from 1..5 770 | * sometimes useful for mapping respond colors etc. 771 | * 772 | * "sugar" properties are also defined for common cases. Currently providing: 773 | * 774 | * - .noContent 775 | * - .badRequest 776 | * - .unauthorized 777 | * - .notAcceptable 778 | * - .notFound 779 | * 780 | * @param {Number} status 781 | * @api private 782 | */ 783 | 784 | Response.prototype.setStatusProperties = function(status){ 785 | var type = status / 100 | 0; 786 | 787 | // status / class 788 | this.status = status; 789 | this.statusType = type; 790 | 791 | // basics 792 | this.info = 1 == type; 793 | this.ok = 2 == type; 794 | this.clientError = 4 == type; 795 | this.serverError = 5 == type; 796 | this.error = (4 == type || 5 == type) 797 | ? this.toError() 798 | : false; 799 | 800 | // sugar 801 | this.accepted = 202 == status; 802 | this.noContent = 204 == status || 1223 == status; 803 | this.badRequest = 400 == status; 804 | this.unauthorized = 401 == status; 805 | this.notAcceptable = 406 == status; 806 | this.notFound = 404 == status; 807 | this.forbidden = 403 == status; 808 | }; 809 | 810 | /** 811 | * Return an `Error` representative of this response. 812 | * 813 | * @return {Error} 814 | * @api public 815 | */ 816 | 817 | Response.prototype.toError = function(){ 818 | var msg = 'got ' + this.status + ' response'; 819 | var err = new Error(msg); 820 | err.status = this.status; 821 | return err; 822 | }; 823 | 824 | /** 825 | * Expose `Response`. 826 | */ 827 | 828 | request.Response = Response; 829 | 830 | /** 831 | * Initialize a new `Request` with the given `method` and `url`. 832 | * 833 | * @param {String} method 834 | * @param {String} url 835 | * @api public 836 | */ 837 | 838 | function Request(method, url) { 839 | var self = this; 840 | Emitter.call(this); 841 | this._query = this._query || []; 842 | this.method = method; 843 | this.url = url; 844 | this.header = {}; 845 | this._header = {}; 846 | this.set('X-Requested-With', 'XMLHttpRequest'); 847 | this.on('end', function(){ 848 | var res = new Response(self.xhr); 849 | if ('HEAD' == method) res.text = null; 850 | self.callback(null, res); 851 | }); 852 | } 853 | 854 | /** 855 | * Inherit from `Emitter.prototype`. 856 | */ 857 | 858 | Request.prototype = new Emitter; 859 | Request.prototype.constructor = Request; 860 | 861 | /** 862 | * Set timeout to `ms`. 863 | * 864 | * @param {Number} ms 865 | * @return {Request} for chaining 866 | * @api public 867 | */ 868 | 869 | Request.prototype.timeout = function(ms){ 870 | this._timeout = ms; 871 | return this; 872 | }; 873 | 874 | /** 875 | * Clear previous timeout. 876 | * 877 | * @return {Request} for chaining 878 | * @api public 879 | */ 880 | 881 | Request.prototype.clearTimeout = function(){ 882 | this._timeout = 0; 883 | clearTimeout(this._timer); 884 | return this; 885 | }; 886 | 887 | /** 888 | * Abort the request, and clear potential timeout. 889 | * 890 | * @return {Request} 891 | * @api public 892 | */ 893 | 894 | Request.prototype.abort = function(){ 895 | if (this.aborted) return; 896 | this.aborted = true; 897 | this.xhr.abort(); 898 | this.clearTimeout(); 899 | this.emit('abort'); 900 | return this; 901 | }; 902 | 903 | /** 904 | * Set header `field` to `val`, or multiple fields with one object. 905 | * 906 | * Examples: 907 | * 908 | * req.get('/') 909 | * .set('Accept', 'application/json') 910 | * .set('X-API-Key', 'foobar') 911 | * .end(callback); 912 | * 913 | * req.get('/') 914 | * .set({ Accept: 'application/json', 'X-API-Key': 'foobar' }) 915 | * .end(callback); 916 | * 917 | * @param {String|Object} field 918 | * @param {String} val 919 | * @return {Request} for chaining 920 | * @api public 921 | */ 922 | 923 | Request.prototype.set = function(field, val){ 924 | if (isObject(field)) { 925 | for (var key in field) { 926 | this.set(key, field[key]); 927 | } 928 | return this; 929 | } 930 | this._header[field.toLowerCase()] = val; 931 | this.header[field] = val; 932 | return this; 933 | }; 934 | 935 | /** 936 | * Get case-insensitive header `field` value. 937 | * 938 | * @param {String} field 939 | * @return {String} 940 | * @api private 941 | */ 942 | 943 | Request.prototype.getHeader = function(field){ 944 | return this._header[field.toLowerCase()]; 945 | }; 946 | 947 | /** 948 | * Set Content-Type to `type`, mapping values from `request.types`. 949 | * 950 | * Examples: 951 | * 952 | * superagent.types.xml = 'application/xml'; 953 | * 954 | * request.post('/') 955 | * .type('xml') 956 | * .send(xmlstring) 957 | * .end(callback); 958 | * 959 | * request.post('/') 960 | * .type('application/xml') 961 | * .send(xmlstring) 962 | * .end(callback); 963 | * 964 | * @param {String} type 965 | * @return {Request} for chaining 966 | * @api public 967 | */ 968 | 969 | Request.prototype.type = function(type){ 970 | this.set('Content-Type', request.types[type] || type); 971 | return this; 972 | }; 973 | 974 | /** 975 | * Set Authorization field value with `user` and `pass`. 976 | * 977 | * @param {String} user 978 | * @param {String} pass 979 | * @return {Request} for chaining 980 | * @api public 981 | */ 982 | 983 | Request.prototype.auth = function(user, pass){ 984 | var str = btoa(user + ':' + pass); 985 | this.set('Authorization', 'Basic ' + str); 986 | return this; 987 | }; 988 | 989 | /** 990 | * Add query-string `val`. 991 | * 992 | * Examples: 993 | * 994 | * request.get('/shoes') 995 | * .query('size=10') 996 | * .query({ color: 'blue' }) 997 | * 998 | * @param {Object|String} val 999 | * @return {Request} for chaining 1000 | * @api public 1001 | */ 1002 | 1003 | Request.prototype.query = function(val){ 1004 | if ('string' != typeof val) val = serialize(val); 1005 | this._query.push(val); 1006 | return this; 1007 | }; 1008 | 1009 | /** 1010 | * Send `data`, defaulting the `.type()` to "json" when 1011 | * an object is given. 1012 | * 1013 | * Examples: 1014 | * 1015 | * // querystring 1016 | * request.get('/search') 1017 | * .end(callback) 1018 | * 1019 | * // multiple data "writes" 1020 | * request.get('/search') 1021 | * .send({ search: 'query' }) 1022 | * .send({ range: '1..5' }) 1023 | * .send({ order: 'desc' }) 1024 | * .end(callback) 1025 | * 1026 | * // manual json 1027 | * request.post('/user') 1028 | * .type('json') 1029 | * .send('{"name":"tj"}) 1030 | * .end(callback) 1031 | * 1032 | * // auto json 1033 | * request.post('/user') 1034 | * .send({ name: 'tj' }) 1035 | * .end(callback) 1036 | * 1037 | * // manual x-www-form-urlencoded 1038 | * request.post('/user') 1039 | * .type('form') 1040 | * .send('name=tj') 1041 | * .end(callback) 1042 | * 1043 | * // auto x-www-form-urlencoded 1044 | * request.post('/user') 1045 | * .type('form') 1046 | * .send({ name: 'tj' }) 1047 | * .end(callback) 1048 | * 1049 | * // defaults to x-www-form-urlencoded 1050 | * request.post('/user') 1051 | * .send('name=tobi') 1052 | * .send('species=ferret') 1053 | * .end(callback) 1054 | * 1055 | * @param {String|Object} data 1056 | * @return {Request} for chaining 1057 | * @api public 1058 | */ 1059 | 1060 | Request.prototype.send = function(data){ 1061 | var obj = isObject(data); 1062 | var type = this.getHeader('Content-Type'); 1063 | 1064 | // merge 1065 | if (obj && isObject(this._data)) { 1066 | for (var key in data) { 1067 | this._data[key] = data[key]; 1068 | } 1069 | } else if ('string' == typeof data) { 1070 | if (!type) this.type('form'); 1071 | type = this.getHeader('Content-Type'); 1072 | if ('application/x-www-form-urlencoded' == type) { 1073 | this._data = this._data 1074 | ? this._data + '&' + data 1075 | : data; 1076 | } else { 1077 | this._data = (this._data || '') + data; 1078 | } 1079 | } else { 1080 | this._data = data; 1081 | } 1082 | 1083 | if (!obj) return this; 1084 | if (!type) this.type('json'); 1085 | return this; 1086 | }; 1087 | 1088 | /** 1089 | * Invoke the callback with `err` and `res` 1090 | * and handle arity check. 1091 | * 1092 | * @param {Error} err 1093 | * @param {Response} res 1094 | * @api private 1095 | */ 1096 | 1097 | Request.prototype.callback = function(err, res){ 1098 | var fn = this._callback; 1099 | if (2 == fn.length) return fn(err, res); 1100 | if (err) return this.emit('error', err); 1101 | fn(res); 1102 | }; 1103 | 1104 | /** 1105 | * Invoke callback with x-domain error. 1106 | * 1107 | * @api private 1108 | */ 1109 | 1110 | Request.prototype.crossDomainError = function(){ 1111 | var err = new Error('Origin is not allowed by Access-Control-Allow-Origin'); 1112 | err.crossDomain = true; 1113 | this.callback(err); 1114 | }; 1115 | 1116 | /** 1117 | * Invoke callback with timeout error. 1118 | * 1119 | * @api private 1120 | */ 1121 | 1122 | Request.prototype.timeoutError = function(){ 1123 | var timeout = this._timeout; 1124 | var err = new Error('timeout of ' + timeout + 'ms exceeded'); 1125 | err.timeout = timeout; 1126 | this.callback(err); 1127 | }; 1128 | 1129 | /** 1130 | * Enable transmission of cookies with x-domain requests. 1131 | * 1132 | * Note that for this to work the origin must not be 1133 | * using "Access-Control-Allow-Origin" with a wildcard, 1134 | * and also must set "Access-Control-Allow-Credentials" 1135 | * to "true". 1136 | * 1137 | * @api public 1138 | */ 1139 | 1140 | Request.prototype.withCredentials = function(){ 1141 | this._withCredentials = true; 1142 | return this; 1143 | }; 1144 | 1145 | /** 1146 | * Initiate request, invoking callback `fn(res)` 1147 | * with an instanceof `Response`. 1148 | * 1149 | * @param {Function} fn 1150 | * @return {Request} for chaining 1151 | * @api public 1152 | */ 1153 | 1154 | Request.prototype.end = function(fn){ 1155 | var self = this; 1156 | var xhr = this.xhr = getXHR(); 1157 | var query = this._query.join('&'); 1158 | var timeout = this._timeout; 1159 | var data = this._data; 1160 | 1161 | // store callback 1162 | this._callback = fn || noop; 1163 | 1164 | // CORS 1165 | if (this._withCredentials) xhr.withCredentials = true; 1166 | 1167 | // state change 1168 | xhr.onreadystatechange = function(){ 1169 | if (4 != xhr.readyState) return; 1170 | if (0 == xhr.status) { 1171 | if (self.aborted) return self.timeoutError(); 1172 | return self.crossDomainError(); 1173 | } 1174 | self.emit('end'); 1175 | }; 1176 | 1177 | // progress 1178 | if (xhr.upload) { 1179 | xhr.upload.onprogress = function(e){ 1180 | e.percent = e.loaded / e.total * 100; 1181 | self.emit('progress', e); 1182 | }; 1183 | } 1184 | 1185 | // timeout 1186 | if (timeout && !this._timer) { 1187 | this._timer = setTimeout(function(){ 1188 | self.abort(); 1189 | }, timeout); 1190 | } 1191 | 1192 | // querystring 1193 | if (query) { 1194 | query = request.serializeObject(query); 1195 | this.url += ~this.url.indexOf('?') 1196 | ? '&' + query 1197 | : '?' + query; 1198 | } 1199 | 1200 | // initiate request 1201 | xhr.open(this.method, this.url, true); 1202 | 1203 | // body 1204 | if ('GET' != this.method && 'HEAD' != this.method && 'string' != typeof data && !isHost(data)) { 1205 | // serialize stuff 1206 | var serialize = request.serialize[this.getHeader('Content-Type')]; 1207 | if (serialize) data = serialize(data); 1208 | } 1209 | 1210 | // set header fields 1211 | for (var field in this.header) { 1212 | if (null == this.header[field]) continue; 1213 | xhr.setRequestHeader(field, this.header[field]); 1214 | } 1215 | 1216 | // send stuff 1217 | xhr.send(data); 1218 | return this; 1219 | }; 1220 | 1221 | /** 1222 | * Expose `Request`. 1223 | */ 1224 | 1225 | request.Request = Request; 1226 | 1227 | /** 1228 | * Issue a request: 1229 | * 1230 | * Examples: 1231 | * 1232 | * request('GET', '/users').end(callback) 1233 | * request('/users').end(callback) 1234 | * request('/users', callback) 1235 | * 1236 | * @param {String} method 1237 | * @param {String|Function} url or callback 1238 | * @return {Request} 1239 | * @api public 1240 | */ 1241 | 1242 | function request(method, url) { 1243 | // callback 1244 | if ('function' == typeof url) { 1245 | return new Request('GET', method).end(url); 1246 | } 1247 | 1248 | // url first 1249 | if (1 == arguments.length) { 1250 | return new Request('GET', method); 1251 | } 1252 | 1253 | return new Request(method, url); 1254 | } 1255 | 1256 | /** 1257 | * GET `url` with optional callback `fn(res)`. 1258 | * 1259 | * @param {String} url 1260 | * @param {Mixed|Function} data or fn 1261 | * @param {Function} fn 1262 | * @return {Request} 1263 | * @api public 1264 | */ 1265 | 1266 | request.get = function(url, data, fn){ 1267 | var req = request('GET', url); 1268 | if ('function' == typeof data) fn = data, data = null; 1269 | if (data) req.query(data); 1270 | if (fn) req.end(fn); 1271 | return req; 1272 | }; 1273 | 1274 | /** 1275 | * GET `url` with optional callback `fn(res)`. 1276 | * 1277 | * @param {String} url 1278 | * @param {Mixed|Function} data or fn 1279 | * @param {Function} fn 1280 | * @return {Request} 1281 | * @api public 1282 | */ 1283 | 1284 | request.head = function(url, data, fn){ 1285 | var req = request('HEAD', url); 1286 | if ('function' == typeof data) fn = data, data = null; 1287 | if (data) req.send(data); 1288 | if (fn) req.end(fn); 1289 | return req; 1290 | }; 1291 | 1292 | /** 1293 | * DELETE `url` with optional callback `fn(res)`. 1294 | * 1295 | * @param {String} url 1296 | * @param {Function} fn 1297 | * @return {Request} 1298 | * @api public 1299 | */ 1300 | 1301 | request.del = function(url, fn){ 1302 | var req = request('DELETE', url); 1303 | if (fn) req.end(fn); 1304 | return req; 1305 | }; 1306 | 1307 | /** 1308 | * PATCH `url` with optional `data` and callback `fn(res)`. 1309 | * 1310 | * @param {String} url 1311 | * @param {Mixed} data 1312 | * @param {Function} fn 1313 | * @return {Request} 1314 | * @api public 1315 | */ 1316 | 1317 | request.patch = function(url, data, fn){ 1318 | var req = request('PATCH', url); 1319 | if ('function' == typeof data) fn = data, data = null; 1320 | if (data) req.send(data); 1321 | if (fn) req.end(fn); 1322 | return req; 1323 | }; 1324 | 1325 | /** 1326 | * POST `url` with optional `data` and callback `fn(res)`. 1327 | * 1328 | * @param {String} url 1329 | * @param {Mixed} data 1330 | * @param {Function} fn 1331 | * @return {Request} 1332 | * @api public 1333 | */ 1334 | 1335 | request.post = function(url, data, fn){ 1336 | var req = request('POST', url); 1337 | if ('function' == typeof data) fn = data, data = null; 1338 | if (data) req.send(data); 1339 | if (fn) req.end(fn); 1340 | return req; 1341 | }; 1342 | 1343 | /** 1344 | * PUT `url` with optional `data` and callback `fn(res)`. 1345 | * 1346 | * @param {String} url 1347 | * @param {Mixed|Function} data or fn 1348 | * @param {Function} fn 1349 | * @return {Request} 1350 | * @api public 1351 | */ 1352 | 1353 | request.put = function(url, data, fn){ 1354 | var req = request('PUT', url); 1355 | if ('function' == typeof data) fn = data, data = null; 1356 | if (data) req.send(data); 1357 | if (fn) req.end(fn); 1358 | return req; 1359 | }; 1360 | 1361 | /** 1362 | * Expose `request`. 1363 | */ 1364 | 1365 | module.exports = request; 1366 | 1367 | }); 1368 | require.alias("component-emitter/index.js", "superagent/deps/emitter/index.js"); 1369 | require.alias("component-emitter/index.js", "emitter/index.js"); 1370 | require.alias("component-indexof/index.js", "component-emitter/deps/indexof/index.js"); 1371 | 1372 | require.alias("RedVentures-reduce/index.js", "superagent/deps/reduce/index.js"); 1373 | require.alias("RedVentures-reduce/index.js", "reduce/index.js"); 1374 | 1375 | require.alias("superagent/lib/client.js", "superagent/index.js"); 1376 | 1377 | if (typeof exports == "object") { 1378 | module.exports = require("superagent"); 1379 | } else if (typeof define == "function" && define.amd) { 1380 | define(function(){ return require("superagent"); }); 1381 | } else { 1382 | this["superagent"] = require("superagent"); 1383 | }})(); 1384 | -------------------------------------------------------------------------------- /Node.js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /Node.js/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | * [Node.js](http://nodejs.org/) 4 | 5 | # Setup 6 | 7 | 1. Enter your app key and secret in the code. (Look for `APP_KEY` and `APP_SECRET`.) 8 | 2. Install dependencies: `npm install` 9 | 10 | # Running 11 | 12 | `node app.js` 13 | 14 | Open the browser to http://127.0.0.1:5000. 15 | -------------------------------------------------------------------------------- /Node.js/app.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'), 2 | express = require('express'), 3 | request = require('request'), 4 | url = require('url'); 5 | 6 | var app = express(); 7 | app.use(express.cookieParser()); 8 | 9 | // insert your app key and secret here 10 | var APP_KEY = ''; 11 | var APP_SECRET = ''; 12 | 13 | function generateCSRFToken() { 14 | return crypto.randomBytes(18).toString('base64') 15 | .replace(/\//g, '-').replace(/\+/g, '_'); 16 | } 17 | 18 | function generateRedirectURI(req) { 19 | return url.format({ 20 | protocol: req.protocol, 21 | host: req.headers.host, 22 | pathname: app.path() + '/callback' 23 | }); 24 | } 25 | 26 | app.get('/', function (req, res) { 27 | var csrfToken = generateCSRFToken(); 28 | res.cookie('csrf', csrfToken); 29 | res.redirect(url.format({ 30 | protocol: 'https', 31 | hostname: 'www.dropbox.com', 32 | pathname: '1/oauth2/authorize', 33 | query: { 34 | client_id: APP_KEY, 35 | response_type: 'code', 36 | state: csrfToken, 37 | redirect_uri: generateRedirectURI(req) 38 | } 39 | })); 40 | }); 41 | 42 | app.get('/callback', function (req, res) { 43 | if (req.query.error) { 44 | return res.send('ERROR ' + req.query.error + ': ' + req.query.error_description); 45 | } 46 | 47 | // check CSRF token 48 | if (req.query.state !== req.cookies.csrf) { 49 | return res.status(401).send( 50 | 'CSRF token mismatch, possible cross-site request forgery attempt.' 51 | ); 52 | } 53 | // exchange access code for bearer token 54 | request.post('https://api.dropbox.com/1/oauth2/token', { 55 | form: { 56 | code: req.query.code, 57 | grant_type: 'authorization_code', 58 | redirect_uri: generateRedirectURI(req) 59 | }, 60 | auth: { 61 | user: APP_KEY, 62 | pass: APP_SECRET 63 | } 64 | }, function (error, response, body) { 65 | var data = JSON.parse(body); 66 | 67 | if (data.error) { 68 | return res.send('ERROR: ' + data.error); 69 | } 70 | 71 | // extract bearer token 72 | var token = data.access_token; 73 | 74 | // use the bearer token to make API calls 75 | request.get('https://api.dropbox.com/1/account/info', { 76 | headers: { Authorization: 'Bearer ' + token } 77 | }, function (error, response, body) { 78 | res.send('Logged in successfully as ' + JSON.parse(body).display_name + '.'); 79 | }); 80 | }); 81 | }); 82 | 83 | app.listen(5000); 84 | -------------------------------------------------------------------------------- /Node.js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "express": "3.x", 4 | "request": "2.x" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PHP/.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | vendor/ 3 | composer.lock 4 | -------------------------------------------------------------------------------- /PHP/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | * PHP 5.4+ 4 | * [Composer](http://getcomposer.org) (`curl -sS https://getcomposer.org/installer | php`) 5 | 6 | # Setup 7 | 8 | 1. Enter your app key and secret in the code. (Look for `APP_KEY` and `APP_SECRET`.) 9 | 2. Install dependencies: `php composer.phar` 10 | 11 | # Running 12 | 13 | `php -S 127.0.0.1:5000 index.php` 14 | 15 | Open the browser to http://127.0.0.1:5000. 16 | -------------------------------------------------------------------------------- /PHP/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": {"slim/slim": "2.*", "rmccue/requests": ">=1.0"}, 3 | "minimum-stability": "dev" 4 | } 5 | -------------------------------------------------------------------------------- /PHP/index.php: -------------------------------------------------------------------------------- 1 | '; 6 | $APP_SECRET = ''; 7 | 8 | $app = new \Slim\Slim(); 9 | date_default_timezone_set('UTC'); 10 | $app->add(new \Slim\Middleware\SessionCookie()); 11 | $env = $app->environment(); 12 | 13 | // Safe base64 encoding for URLs. 14 | function base64url_encode($data) { 15 | return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 16 | } 17 | // Generate a random token used for CSRF protection. 18 | function generateCSRFToken() { 19 | return base64url_encode(openssl_random_pseudo_bytes(18)); 20 | } 21 | 22 | // Generate a redirect URI corresponding to the given route. 23 | // Note that OAuth 2.0 only allows HTTPS URLs in general, but Dropbox 24 | // allows HTTP URLs for localhost/127.0.0.1 endpoints only. 25 | function generate_redirect_uri($route_name) { 26 | $app = \Slim\Slim::getInstance(); 27 | $env = $app->environment(); 28 | 29 | return $env['slim.url_scheme'] . '://' . 30 | $_SERVER['HTTP_HOST'] . $app->urlFor($route_name); 31 | } 32 | 33 | // Main endpoint for the app. This page just starts the OAuth flow by 34 | // redirecting the user to Dropbox to sign in (if necessary) and allow 35 | // the app's request for access. 36 | $app->get('/', function () use ($app) { 37 | $csrfToken = generateCSRFToken(); 38 | $_SESSION['csrfToken'] = $csrfToken; 39 | // Redirect to the OAuth authorize endpoint, using the authorization 40 | // code flow. 41 | $app->redirect('https://www.dropbox.com/1/oauth2/authorize?' . 42 | http_build_query(array( 43 | 'response_type' => 'code', 44 | 'client_id' => $GLOBALS['APP_KEY'], 45 | 'redirect_uri' => generate_redirect_uri('callback'), 46 | 'state' => $csrfToken 47 | ))); 48 | }); 49 | 50 | // OAuth callback URL, which the user is redirected to by Dropbox after 51 | // allowing access to the app. The query parameters will include an 52 | // access code, which is then exchanged for an access token. The access 53 | // token is what's used to make calls to the Dropbox API. 54 | $app->get('/callback', function () use ($app, $env) { 55 | $params = array(); 56 | parse_str($env['QUERY_STRING'], $params); 57 | 58 | // If there's an error, display it. 59 | if (isset($params['error'])) { 60 | echo 'Received an "' . $params['error'] . '" error with the message "' . $params['error_description'] . '"'; 61 | return; 62 | } 63 | 64 | // Check that the CSRF token matches. 65 | if ($params['state'] != $_SESSION['csrfToken']) { 66 | echo 'CSRF protection error! State parameter doesn\'t match CSRF token'; 67 | return; 68 | } 69 | 70 | $token_request = Requests::post('https://www.dropbox.com/1/oauth2/token', 71 | array(), // headers 72 | array( // form body 73 | 'code' => $params['code'], 74 | 'grant_type' => 'authorization_code', 75 | 'client_id' => $GLOBALS['APP_KEY'], 76 | 'client_secret' => $GLOBALS['APP_SECRET'], 77 | // This redirect URI must exactly match the one used when 78 | // calling the authorize endpoint previously. 79 | 'redirect_uri' => generate_redirect_uri('callback') 80 | )); 81 | if ($token_request->status_code !== 200) { 82 | echo 'Error, possibly expired token.'; 83 | return; 84 | } 85 | // Get the bearer token from the response. 86 | $json = json_decode($token_request->body, true); 87 | $token = $json['access_token']; 88 | 89 | // Use the bearer token to make calls to the Dropbox API. 90 | $info_request = Requests::get('https://api.dropbox.com/1/account/info', 91 | array( // headers 92 | 'Authorization' => 'Bearer ' . $token 93 | )); 94 | 95 | $json = json_decode($info_request->body, true); 96 | $name = $json['display_name']; 97 | 98 | echo 'Successfully authenticated as ' . $name . '.'; 99 | })->name('callback'); 100 | 101 | $app->run(); 102 | ?> 103 | -------------------------------------------------------------------------------- /Perl/.gitignore: -------------------------------------------------------------------------------- 1 | extlib/ 2 | -------------------------------------------------------------------------------- /Perl/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | * Perl 4 | * [cpanminus](https://github.com/miyagawa/cpanminus) 5 | * [local::lib](https://metacpan.org/module/local::lib) 6 | 7 | # Setup 8 | 9 | 1. Enter your app key and secret in the code. (Look for `APP_KEY` and `APP_SECRET`.) 10 | 2. Install dependencies: `cpanm -L extlib Dancer JSON Bytes::Random::Secure HTTP::Tiny Net::SSLeay Mozilla::CA` 11 | 12 | # Running 13 | 14 | `perl app.pl` 15 | 16 | Open the browser to http://127.0.0.1:5000. 17 | -------------------------------------------------------------------------------- /Perl/app.pl: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use local::lib 'extlib'; 4 | use Dancer; 5 | use Bytes::Random::Secure qw(random_bytes_base64); 6 | use URI; 7 | use HTTP::Tiny; 8 | 9 | my $APP_KEY = ''; 10 | my $APP_SECRET = ''; 11 | 12 | set 'port' => 5000; 13 | 14 | get '/' => sub { 15 | my $csrf = random_bytes_base64(18); 16 | $csrf =~ tr/+\/=\n/-_/; 17 | cookie csrf => $csrf; 18 | my $u = URI->new('https://www.dropbox.com/1/oauth2/authorize'); 19 | $u->query_form( 20 | client_id => $APP_KEY, 21 | redirect_uri => uri_for('/callback'), 22 | response_type => 'code', 23 | state => $csrf, 24 | ); 25 | redirect $u->as_string; 26 | }; 27 | 28 | get '/callback' => sub { 29 | my $csrf = cookie 'csrf'; 30 | cookie csrf => '', expires => -1; 31 | send_error('Possible CSRF attack.', 401) unless defined $csrf and param('state') eq $csrf; 32 | 33 | my $http = HTTP::Tiny->new(verify_SSL => 1); 34 | 35 | my $response = $http->post_form("http://$APP_KEY:$APP_SECRET\@api.dropbox.com/1/oauth2/token", { 36 | redirect_uri => uri_for('/callback'), 37 | code => param('code'), 38 | grant_type => 'authorization_code', 39 | }); 40 | my $token = from_json($response->{content})->{access_token}; 41 | 42 | $response = $http->get('https://api.dropbox.com/1/account/info', { 43 | headers => {Authorization => "Bearer $token"} 44 | }); 45 | my $name = from_json($response->{content})->{display_name}; 46 | 47 | return "Successfully authenticated as $name."; 48 | }; 49 | 50 | start; 51 | -------------------------------------------------------------------------------- /Python/.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | -------------------------------------------------------------------------------- /Python/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | * [Python 2.7+](http://python.org/) 4 | * [pip](http://www.pip-installer.org/en/latest/installing.html) 5 | 6 | # Setup 7 | 8 | 1. Enter your app key and secret in the code. (Look for `APP_KEY` and `APP_SECRET`.) 9 | 2. Change the `app.secret_key` to something random (and secret). 10 | 3. Install dependencies: 11 | * (recommended) `virtualenv venv && source venv/bin/activate` 12 | * `pip install -r requirements.txt` 13 | 14 | # Running 15 | 16 | `python app.py` 17 | 18 | Open the browser to http://127.0.0.1:5000. 19 | -------------------------------------------------------------------------------- /Python/app.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from flask import abort, Flask, redirect, request, session, url_for 3 | import os 4 | import requests 5 | import urllib 6 | 7 | app = Flask(__name__) 8 | app.secret_key = 'ChangeThisToSomethingRandom' 9 | 10 | APP_KEY = ''; 11 | APP_SECRET = ''; 12 | 13 | @app.route('/') 14 | def index(): 15 | csrf_token = base64.urlsafe_b64encode(os.urandom(18)) 16 | session['csrf_token'] = csrf_token 17 | return redirect('https://www.dropbox.com/1/oauth2/authorize?%s' % urllib.urlencode({ 18 | 'client_id': APP_KEY, 19 | 'redirect_uri': url_for('callback', _external=True), 20 | 'response_type': 'code', 21 | 'state': csrf_token 22 | })) 23 | 24 | @app.route('/callback') 25 | def callback(): 26 | if request.args['state'] != session.pop('csrf_token'): 27 | abort(403) 28 | data = requests.post('https://api.dropbox.com/1/oauth2/token', 29 | data={ 30 | 'code': request.args['code'], 31 | 'grant_type': 'authorization_code', 32 | 'redirect_uri': url_for('callback', _external=True) 33 | }, 34 | auth=(APP_KEY, APP_SECRET)).json() 35 | token = data['access_token'] 36 | 37 | info = requests.get('https://api.dropbox.com/1/account/info', headers={'Authorization': 'Bearer %s' % token}).json() 38 | return 'Successfully authenticated as %s.' % info['display_name'] 39 | 40 | if __name__=='__main__': 41 | app.run(debug=True) 42 | -------------------------------------------------------------------------------- /Python/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.10.1 2 | requests==1.2.3 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Note: The samples in this repo use the /1/account/info endpoint in Dropbox API v1, which has been retired. [Learn more.](https://blogs.dropbox.com/developers/2017/09/api-v1-shutdown-details/) The OAuth 2 implementations can still be used for [Dropbox API v2](https://www.dropbox.com/developers) though.** 2 | 3 | Dropbox and OAuth 2 the Hard Way 4 | -------------------------------- 5 | 6 | This project shows, in a variety of languages, how to authenticate a user and call the Dropbox API *without* using existing OAuth or Dropbox libraries. 7 | 8 | Code is provided for the following languages: 9 | 10 | * [C#](C%23) - ASP.NET MVC 4, HttpClient 11 | * [Go](Go) - (no libraries) 12 | * [Java](Java) - Spark, HttpClient 13 | * [JavaScript (browser)](JavaScript) - Superagent 14 | * [JavaScript (Node.js)](Node.js) - Express, Request 15 | * [PHP](PHP) - Slim, Requests 16 | * [Perl](Perl) - Dancer, HTTP::Tiny 17 | * [Python](Python) - Flask, Requests 18 | * [Ruby](Ruby) - Sinatra, Rest-Client 19 | 20 | To run the samples, you'll need to [create a Dropbox API app](https://www.dropbox.com/developers/apps) and put your app key and secret into the code. You'll also need to set up the right OAuth 2 callback URL (`http://127.0.0.1:5000/callback` for most samples, `http://localhost:5000/callback` for C# and `http://127.0.0.1:5000` for JavaScript). 21 | 22 | FAQ 23 | === 24 | 25 | Why? 26 | ---- 27 | 28 | There are [lots of libraries](https://www.dropbox.com/developers/core) for using the Dropbox Core API, but some languages don't have a library, and libraries don't always cover every option of every API method. Fortunately, the API is pretty simple, and OAuth 2 (which is used for authentication) is also pretty simple. By reading through these examples, a developer familiar with basic HTTP APIs should be able to write their own code for interacting with the Dropbox API without having to rely on an existing library. 29 | 30 | It's also kind of fun and instructive to read and write the same app in multiple programming languages. 31 | 32 | Your code sucks. 33 | ---------------- 34 | Good question! I'm not an expert in most of these languages—for example, this was my first time writing Go—so it's quite likely that I got some code wrong or failed to follow some language idioms. Please send me a pull request if you have suggestions for how to improve the code. 35 | 36 | What about language X? 37 | ---------------------- 38 | 39 | Let me know by [opening an issue](https://github.com/dropbox/othw/issues) if there's another language you'd like to see a sample for. Better yet, write it yourself and send a pull request! 40 | -------------------------------------------------------------------------------- /Ruby/.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | vendor/ 3 | .bundle/ 4 | -------------------------------------------------------------------------------- /Ruby/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'sinatra' 4 | gem 'rest-client' 5 | gem 'json' 6 | -------------------------------------------------------------------------------- /Ruby/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | * [Ruby 1.8.7+](http://www.ruby-lang.org) 4 | * [Bundler](http://bundler.io/) 5 | 6 | # Setup 7 | 8 | 1. Enter your app key and secret in the code. (Look for `APP_KEY` and `APP_SECRET`.) 9 | 2. Install dependencies: `bundle install` 10 | 11 | # Running 12 | 13 | `ruby app.rb` 14 | 15 | Open the browser to http://127.0.0.1:5000. 16 | -------------------------------------------------------------------------------- /Ruby/app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler/setup' 3 | require 'sinatra' 4 | require 'rest_client' 5 | require 'cgi' 6 | require 'securerandom' 7 | require 'json' 8 | 9 | set :port, 5000 10 | enable :sessions 11 | 12 | APP_KEY = '' 13 | APP_SECRET = '' 14 | 15 | get '/' do 16 | csrf_token = SecureRandom.base64(18).tr('+/', '-_').gsub(/=*$/, '') 17 | session[:csrf] = csrf_token 18 | 19 | params = { 20 | :client_id => APP_KEY, 21 | :response_type => :code, 22 | :redirect_uri => uri('/callback'), 23 | :state => csrf_token 24 | } 25 | query = params.map { |k, v| "#{k.to_s}=#{CGI.escape(v.to_s)}" }.join '&' 26 | redirect "https://www.dropbox.com/1/oauth2/authorize?#{query}" 27 | end 28 | 29 | get '/callback' do 30 | csrf_token = session.delete(:csrf) 31 | if params[:state] != csrf_token then 32 | halt 401, 'Possible CSRF attack.' 33 | end 34 | 35 | response = RestClient.post "https://#{APP_KEY}:#{APP_SECRET}@api.dropbox.com/1/oauth2/token", 36 | :code => params[:code], 37 | :redirect_uri => uri('/callback'), 38 | :grant_type => 'authorization_code' 39 | token = JSON.parse(response.to_str)['access_token'] 40 | 41 | info = JSON.parse(RestClient.get('https://api.dropbox.com/1/account/info', :Authorization => "Bearer #{token}")) 42 | 43 | "Successfully authenticated as #{info['display_name']}." 44 | end 45 | --------------------------------------------------------------------------------