Skip to content

Commit

Permalink
Added Documentation for advhttp and oat
Browse files Browse the repository at this point in the history
Added the ability to specify scope in oat
OAT version moved to 1.1.0
  • Loading branch information
murphysean committed Jun 19, 2015
1 parent 0122cc4 commit ee6085a
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 16 deletions.
118 changes: 116 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
91 changes: 91 additions & 0 deletions oat/README.md
Original file line number Diff line number Diff line change
@@ -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.
40 changes: 26 additions & 14 deletions oat/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)
Expand All @@ -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 <client-name>] [--user <user-name>]")
fmt.Fprintln(os.Stderr, "\t[--username <username>] [--password <password>]")
fmt.Fprintln(os.Stderr, "\t[--scope <space seperated list of scope requests>]")
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, "")
Expand Down Expand Up @@ -157,6 +159,7 @@ func main() {

var selectedClient *Client = nil
var selectedUser *User = nil
var selectedScope []string = make([]string, 0)

selectedClient = clients[*oatClient]

Expand All @@ -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...
Expand All @@ -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 {
Expand All @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit ee6085a

Please sign in to comment.