From ee6085a28a8bcfb8da5a56413df7f1de76e8ff2d Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Fri, 19 Jun 2015 12:47:27 -0600 Subject: [PATCH] Added Documentation for advhttp and oat Added the ability to specify scope in oat OAT version moved to 1.1.0 --- README.md | 118 +++++++++++++++++++++++++++++++++++++++++++++++++- oat/README.md | 91 ++++++++++++++++++++++++++++++++++++++ oat/main.go | 40 +++++++++++------ 3 files changed, 233 insertions(+), 16 deletions(-) create mode 100644 oat/README.md diff --git a/README.md b/README.md index 5157484..65c8c1b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,118 @@ advhttp -======= +=== -Go Library that contains some helper options for +Advhttp is a go utility library to help out with some common +http needs that aren't in the standard library. The library +can be broken up into the following functionality categories. + +- Handlers +- Logging +- OAuth2 +- Cross Origin Resource Sharing +- Reverse Proxy + +Handlers +--- + +The included handlers try to build a handler stack to alleviate +boilerplate code in go server applications. A common setup +might look like: + + ch := advhttp.NewDefaultCorsHandler(http.DefaultServeMux) + ph := advhttp.NewPanicRecoveryHandler(ch) + lh := advhttp.NewLoggingHandler(ph, os.Stdout) + + http.ListenAndServe(":http", lh) + +This creates a request pipeline that will eventually end up using +the default serve mux provided in the http library. But you'll +have Common Log Format logs printed to std out, panic recovery, +and cors headers to allow cross origin resouce sharing. + +Logging +--- + +You can also bypass the handlers and get a little bit more out of +the library on your own. I provide a custom ResponseWriter that +tracks bytes written and status code so that you can have those +after you write. These are typically included in logs. + + trw := advhttp.NewResponseWriter(w) + fmt.Fprintf(os.Stderr, "Status: %v\n", trw.Status()) + fmt.Fprintf(os.Stderr, "Bytes Written: %v\n", trw.Length()) + fmt.Fprint(os.Stdout, trw.LogCommonExtended(r)) + +OAuth2 +--- + +The OAuth2 portion of the library is primarily for applications +which may need to communicate with other apis via oauth2. The +library will cache a token so that it may be re-used accross +multiple calls and reduce latency. + + tracker, err := advhttp.NewClientCredentialsTokenTracker(tokenEndpoint, tokenInfoEndpoint, client_id, client_secret, scope) + token, err := tracker.GetToken() //Get a new token or a cached token + token, err := tracker.GetNewToken() //Force getting a new token + ti, err := tracker.GetTokenInfo() //Return an object with the result from the tokenInfoEndpoint + + //You can also just use the apis yourself + token, tokenExpires, refreshToken, err := advhttp.GetPasswordToken(tokenEndpoint, client_id, client_secret, username, password, scope) + token, tokenExpires, err := advhttp.GetRefreshToken(tokenEndpoint, client_id, client_secret, refresh_token, scope) + token, tokenExpires, err := advhttp.GetClientCredentialsToken(tokenEndpoint, client_id, client_secret string, scope []string) + ti, err := advhttp.GetTokenInformation(tokenInfoEndpoint, token string) + +Cross Origin Resource Sharing +--- + +The cors library allows you to set up cors how you'd like, and then +serve based on that. advhttp comes with a DefaultCors object that +contains the default cors settings. `advhttp.DefaultCors`. A cors +object includes: + +- AllowOrigin (string) Default "" meaning to mirror the Origin header +- AllowHeaders ([]string) Default advhttp.CorsDefaultAllowHeaders +- AllowMethods ([]string) Default advhttp.CorsDefaultAllowMethods +- ExposeHeaders ([]string) Default advhttp.CorsDefaultExposeHeaders +- MaxAge (int64) Default advhttp.CorsDefaultMaxAge +- AllowCredentials (bool) Default advhttp.CorsDefaultAllowCredentials + +You can use the default cors handler: + + advhttp.ProcessCors(w,r) + +Or you can create your own: + + cors := new(Cors) + cors.AllowOrigin = "*" + cors.AllowHeaders = []string{"Location", "Content-Type", "ETag", "Accept-Patch"} + cors.AllowMethods = []string{"OPTIONS", "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE"} + cors.ExposeHeaders = []string{"Location", "Content-Type", "ETag", "Accept-Patch"} + cors.MaxAge = int64(1728000) + cors.AllowCredentials = true + cors.ProcessCors(w,r) + + +Or you can use the included handler: + + ch := advhttp.NewDefaultCorsHandler(http.DefaultServeMux) + ch.ServeHTTP(w,r) + +Reverse Proxy +--- + +The reverse proxy adds to the httputils and allows you to reverse proxy +to different hosts, mutate the path on it's way there, (and also in the +Location header in responses). It is helpful in gateway projects. + + googleURL, _ := url.Parse("http://www.google.com/") + downloads := advhttp.NewGatewayReverseProxy(googleURL, true, "/google/") + http.Handle("/google/", someHandlerFunc) + +In the above example, calls to the /google/ endpoint on your server +`http://yourserver.com/google/` will result in your server then calling +google `http://www.google.com/` but stripping off the /google/ path +from the original url. If the response from google is a redirect or +includes the `Location` header, then advhttp will rewrite the header +to include the original `/google/` path. This results in browser being +able to (almost) transparently use the other server through your +server. diff --git a/oat/README.md b/oat/README.md new file mode 100644 index 0000000..b8391f4 --- /dev/null +++ b/oat/README.md @@ -0,0 +1,91 @@ +OAT +=== + +OAT (short for OAuth Token) is a command line utility wrapper around +advhttp's oauth2Client library. It is designed to make working with +oauth2 simple. It currently supports the following oauth2 Grant +Types: + +- Client Credentials +- Password + +Getting Started +--- + +The first step will be to create your OAT config file. OAT reads this +file to find clients and users based on the command line selectors. + + touch ~/.oatconfig + +You will need at least a client in the file. The file format follows +the ini file format. A client includes: + +1. Required + 1. client\_id (string) + 1. client\_secret (string) + 1. token\_endpoint (url) +1. Optional + 1. token\_info\_endpoint (url) + 1. scope (A space seperated list of requested scopes) + +You can also include a user in the oat config file. When requested +a user will be used in a `password` grant type. A user includes: + +1. username (string) +1. password (string) + +Example File contents: + + [myclient] + client_id=clientid + client_secret=clientsecret + token_endpoint=https://www.example.com/oauth2/token + token_info_endpoint=https://www.example.com/oauth2/tokeninfo + scope=https://example.com/auth/scope1 https://example.com/auth/scope2 + [myuser] + username=username + password=password + +Using OAT +--- + +Now that you have your oat file configured, you can use oat. There +is a help document included with oat, to use it type `oat -h`. OAT +has a number of flags that you can set: + +1. -c or --client (string) index into ~/.oatconfig file +1. -u or --user (string) index into ~/.oatconfig file +1. -s or --scope (string seperated list) provide scope +1. -U or --username (string) provide username +1. -p or --password (string) provide password +1. -i or --tokeninfo (bool) return token info object +1. -n or --nonewline (bool) don't print newline after token +1. -v or --verbose (bool) turn on verbose output + +Some Examples: +--- + +Our first example will get a new client\_credentials token on behalf +of the 'myclient' in the example file: + + oat -c myclient + +Our next example will get a new password token on behalf of the +'myclient' client and the 'myuser' user in the example file: + + oat -c myclient -u myuser + +This next example will get a password token on behalf of the 'myclient' +client and then provide user credentials on the command line and also +override the default client scope with new scope on the command line: + + oat -c myclient -s 'https://example.com/auth/scope3 https://example.com/auth/scope4' -U user1 -p password + +The final example will get a new token and utilize that token in a curl +command: + + curl https://api.example.com/cool/story/bro -H "Authorization: Bearer `oat -nc myclient`" + +Notice in this example that we used the -n toggle to turn off printing +a newline and we used the backticks to execute the command and then +dump the response back into the input of the curl command. diff --git a/oat/main.go b/oat/main.go index 5caf3e8..79593f0 100644 --- a/oat/main.go +++ b/oat/main.go @@ -14,17 +14,18 @@ import ( ) const ( - VERSION = "1.0.0" + VERSION = "1.1.0" ) var ( clients = make(map[string]*Client) users = make(map[string]*User) - oatClient = flag.StringP("client", "c", "", "Select the client (by name) from the config to use for this exectution.") - oatUser = flag.StringP("user", "u", "", "Select the user (by name) from the config to use for this execution.") - username = flag.StringP("username", "U", "", "Specify a username that will be used to authenticate and obtain a user access token.") - password = flag.StringP("password", "p", "", "Specify a password that will be used to authenticate and obtain a user access token.") - printTokenInfo = flag.BoolP("tokeninfo", "i", false, "Print out the json tokeninfo rather than the token itself.") + oatClient = flag.StringP("client", "c", "", "Select the client (by name) from the config to use for this exectution") + oatUser = flag.StringP("user", "u", "", "Select the user (by name) from the config to use for this execution") + oatScope = flag.StringP("scope", "s", "", "Ask for a different set of scopes than the client default") + username = flag.StringP("username", "U", "", "Specify a username that will be used to authenticate and obtain a user access token") + password = flag.StringP("password", "p", "", "Specify a password that will be used to authenticate and obtain a user access token") + printTokenInfo = flag.BoolP("tokeninfo", "i", false, "Print out the json tokeninfo rather than the token itself") nonewline = flag.BoolP("nonewline", "n", false, "Output the token with (n)o new line") verbose = flag.BoolP("verbose", "v", false, "Print extra information to stderr") ) @@ -47,16 +48,17 @@ func main() { fmt.Fprintf(os.Stderr, "Usage of %s v%s:\n", os.Args[0], VERSION) fmt.Fprintln(os.Stderr, "oat [--client ] [--user ]") fmt.Fprintln(os.Stderr, "\t[--username ] [--password ]") + fmt.Fprintln(os.Stderr, "\t[--scope ]") fmt.Fprintln(os.Stderr, "\t[--nonewline] [--tokeninfo]") - fmt.Fprintln(os.Stderr, "\t[-c -u -U -p -n -i]") + fmt.Fprintln(os.Stderr, "\t[-c -u -s -U -p -n -i]") fmt.Fprintln(os.Stderr, "\t['help']") fmt.Fprintln(os.Stderr, "") flag.PrintDefaults() fmt.Fprintln(os.Stderr, "") - fmt.Fprintln(os.Stderr, "OAT (O Auth Token is a small cli program designed to quickly fetch ") + fmt.Fprintln(os.Stderr, "OAT (O Auth Token) is a small cli program designed to quickly fetch ") fmt.Fprintln(os.Stderr, "oauth2 tokens from a server for api access. It uses a config file, ") - fmt.Fprintln(os.Stderr, "'.oatconfig', stored in your home directory to select credentials ") + fmt.Fprintln(os.Stderr, "'~/.oatconfig', stored in your home directory to select credentials ") fmt.Fprintln(os.Stderr, "to use. The format of the config file is the fairly standard .ini ") fmt.Fprintln(os.Stderr, "format.") fmt.Fprintln(os.Stderr, "") @@ -157,6 +159,7 @@ func main() { var selectedClient *Client = nil var selectedUser *User = nil + var selectedScope []string = make([]string, 0) selectedClient = clients[*oatClient] @@ -167,7 +170,6 @@ func main() { if *verbose { fmt.Fprintf(os.Stderr, "Using Client: %v\n\tid: %v\n\tep: %v\n", *oatClient, selectedClient.Id, selectedClient.Tokenep) - fmt.Fprintf(os.Stderr, "Using Scope: \n\t%v\n", strings.Join(selectedClient.Scope, "\n\t")) } //If a command line -u is found, use that user, else... @@ -189,6 +191,16 @@ func main() { fmt.Fprintf(os.Stderr, "Using User:%v\tusername:%v\n", *oatUser, selectedUser.Username) } + if *oatScope != "" { + selectedScope = strings.Split(*oatScope, " ") + } else { + selectedScope = selectedClient.Scope + } + + if *verbose { + fmt.Fprintf(os.Stderr, "Using Scope: \n\t%v\n", strings.Join(selectedScope, "\n\t")) + } + var token string var err error if selectedUser == nil { @@ -199,9 +211,9 @@ func main() { os.Exit(1) } cURL.User = url.UserPassword(selectedClient.Id, selectedClient.Secret) - fmt.Fprintf(os.Stderr, "curl \"%v\" -d 'grant_type=client_credentials' -d 'scope=%v'\n\n", cURL.String(), strings.Join(selectedClient.Scope, " ")) + fmt.Fprintf(os.Stderr, "curl \"%v\" -d 'grant_type=client_credentials' -d 'scope=%v'\n\n", cURL.String(), strings.Join(selectedScope, " ")) } - token, _, err = advhttp.GetClientCredentialsToken(selectedClient.Tokenep, selectedClient.Id, selectedClient.Secret, selectedClient.Scope) + token, _, err = advhttp.GetClientCredentialsToken(selectedClient.Tokenep, selectedClient.Id, selectedClient.Secret, selectedScope) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -215,9 +227,9 @@ func main() { } cURL.User = url.UserPassword(selectedClient.Id, selectedClient.Secret) fmt.Fprintf(os.Stderr, "curl \"%v\" -d 'grant_type=password' -d 'username=%v' -d 'password=%v' -d 'scope=%v'\n\n", - cURL.String(), selectedUser.Username, selectedUser.Password, strings.Join(selectedClient.Scope, " ")) + cURL.String(), selectedUser.Username, selectedUser.Password, strings.Join(selectedScope, " ")) } - token, _, _, err = advhttp.GetPasswordToken(selectedClient.Tokenep, selectedClient.Id, selectedClient.Secret, selectedUser.Username, selectedUser.Password, selectedClient.Scope) + token, _, _, err = advhttp.GetPasswordToken(selectedClient.Tokenep, selectedClient.Id, selectedClient.Secret, selectedUser.Username, selectedUser.Password, selectedScope) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1)