Merge branch 'master' of https://github.com/bitly/oauth2_proxy into work

This commit is contained in:
Pavel Sorokin 2017-12-11 12:21:22 +00:00
commit 706c109ac0
10 changed files with 249 additions and 100 deletions

View File

@ -16,6 +16,11 @@ to validate accounts by email, domain or group.
## Installation ## Installation
1. Download [Prebuilt Binary](https://github.com/bitly/oauth2_proxy/releases) (current release is `v2.2`) or build with `$ go get github.com/bitly/oauth2_proxy` which will put the binary in `$GOROOT/bin` 1. Download [Prebuilt Binary](https://github.com/bitly/oauth2_proxy/releases) (current release is `v2.2`) or build with `$ go get github.com/bitly/oauth2_proxy` which will put the binary in `$GOROOT/bin`
Prebuilt binaries can be validated by extracting the file and verifying it against the `sha256sum.txt` checksum file provided for each release starting with version `v2.3`.
```
sha256sum -c sha256sum.txt 2>&1 | grep OK
oauth2_proxy-2.3.linux-amd64: OK
```
2. Select a Provider and Register an OAuth Application with a Provider 2. Select a Provider and Register an OAuth Application with a Provider
3. Configure OAuth2 Proxy using config file, command line options, or environment variables 3. Configure OAuth2 Proxy using config file, command line options, or environment variables
4. Configure SSL or Deploy behind a SSL endpoint (example provided for Nginx) 4. Configure SSL or Deploy behind a SSL endpoint (example provided for Nginx)
@ -225,6 +230,7 @@ Usage of oauth2_proxy:
-redeem-url string: Token redemption endpoint -redeem-url string: Token redemption endpoint
-redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback" -redirect-url string: the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
-request-logging: Log requests to stdout (default true) -request-logging: Log requests to stdout (default true)
-request-logging-format: Template for request log lines (see "Logging Format" paragraph below)
-resource string: The resource that is protected (Azure AD only) -resource string: The resource that is protected (Azure AD only)
-scope string: OAuth scope specification -scope string: OAuth scope specification
-set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode) -set-xauthrequest: set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)
@ -362,12 +368,21 @@ following:
## Logging Format ## Logging Format
OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log. By default, OAuth2 Proxy logs requests to stdout in a format similar to Apache Combined Log.
``` ```
<REMOTE_ADDRESS> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] <HOST_HEADER> GET <UPSTREAM_HOST> "/path/" HTTP/1.1 "<USER_AGENT>" <RESPONSE_CODE> <RESPONSE_BYTES> <REQUEST_DURATION> <REMOTE_ADDRESS> - <user@domain.com> [19/Mar/2015:17:20:19 -0400] <HOST_HEADER> GET <UPSTREAM_HOST> "/path/" HTTP/1.1 "<USER_AGENT>" <RESPONSE_CODE> <RESPONSE_BYTES> <REQUEST_DURATION>
``` ```
If you require a different format than that, you can configure it with the `-request-logging-format` flag.
The default format is configured as follows:
```
{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}
```
[See `logMessageData` in `logging_handler.go`](./logging_handler.go) for all available variables.
## Adding a new Provider ## Adding a new Provider
Follow the examples in the [`providers` package](providers/) to define a new Follow the examples in the [`providers` package](providers/) to define a new

View File

@ -11,7 +11,6 @@ import (
) )
func Request(req *http.Request) (*simplejson.Json, error) { func Request(req *http.Request) (*simplejson.Json, error) {
log.Printf("New request to: '%s'", req.URL)
resp, err := http.DefaultClient.Do(req) resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
log.Printf("%s %s %s", req.Method, req.URL, err) log.Printf("%s %s %s", req.Method, req.URL, err)

23
dist.sh
View File

@ -5,14 +5,13 @@ set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "working dir $DIR" echo "working dir $DIR"
mkdir -p $DIR/dist mkdir -p $DIR/dist
mkdir -p $DIR/.godeps dep ensure || exit 1
export GOPATH=$DIR/.godeps:$GOPATH
GOPATH=$DIR/.godeps gpm install
os=$(go env GOOS) os=$(go env GOOS)
arch=$(go env GOARCH) arch=$(go env GOARCH)
version=$(cat $DIR/version.go | grep "const VERSION" | awk '{print $NF}' | sed 's/"//g') version=$(cat $DIR/version.go | grep "const VERSION" | awk '{print $NF}' | sed 's/"//g')
goversion=$(go version | awk '{print $3}') goversion=$(go version | awk '{print $3}')
sha256sum=()
echo "... running tests" echo "... running tests"
./test.sh ./test.sh
@ -25,10 +24,22 @@ for os in windows linux darwin; do
fi fi
BUILD=$(mktemp -d ${TMPDIR:-/tmp}/oauth2_proxy.XXXXXX) BUILD=$(mktemp -d ${TMPDIR:-/tmp}/oauth2_proxy.XXXXXX)
TARGET="oauth2_proxy-$version.$os-$arch.$goversion" TARGET="oauth2_proxy-$version.$os-$arch.$goversion"
FILENAME="oauth2_proxy-$version.$os-$arch$EXT"
GOOS=$os GOARCH=$arch CGO_ENABLED=0 \ GOOS=$os GOARCH=$arch CGO_ENABLED=0 \
go build -ldflags="-s -w" -o $BUILD/$TARGET/oauth2_proxy$EXT || exit 1 go build -ldflags="-s -w" -o $BUILD/$TARGET/$FILENAME || exit 1
pushd $BUILD pushd $BUILD/$TARGET
tar czvf $TARGET.tar.gz $TARGET sha256sum+=("$(shasum -a 256 $FILENAME || exit 1)")
cd .. && tar czvf $TARGET.tar.gz $TARGET
mv $TARGET.tar.gz $DIR/dist mv $TARGET.tar.gz $DIR/dist
popd popd
done done
checksum_file="sha256sum.txt"
cd $DIR/dist
if [ -f $checksum_file ]; then
rm $checksum_file
fi
touch $checksum_file
for checksum in "${sha256sum[@]}"; do
echo "$checksum" >> $checksum_file
done

View File

@ -9,9 +9,14 @@ import (
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"text/template"
"time" "time"
) )
const (
defaultRequestLoggingFormat = "{{.Client}} - {{.Username}} [{{.Timestamp}}] {{.Host}} {{.RequestMethod}} {{.Upstream}} {{.RequestURI}} {{.Protocol}} {{.UserAgent}} {{.StatusCode}} {{.ResponseSize}} {{.RequestDuration}}"
)
// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status
// code and body size // code and body size
type responseLogger struct { type responseLogger struct {
@ -64,15 +69,38 @@ func (l *responseLogger) Size() int {
return l.size return l.size
} }
// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends // logMessageData is the container for all values that are available as variables in the request logging format.
type loggingHandler struct { // All values are pre-formatted strings so it is easy to use them in the format string.
writer io.Writer type logMessageData struct {
handler http.Handler Client,
enabled bool Host,
Protocol,
RequestDuration,
RequestMethod,
RequestURI,
ResponseSize,
StatusCode,
Timestamp,
Upstream,
UserAgent,
Username string
} }
func LoggingHandler(out io.Writer, h http.Handler, v bool) http.Handler { // loggingHandler is the http.Handler implementation for LoggingHandlerTo and its friends
return loggingHandler{out, h, v} type loggingHandler struct {
writer io.Writer
handler http.Handler
enabled bool
logTemplate *template.Template
}
func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
return loggingHandler{
writer: out,
handler: h,
enabled: v,
logTemplate: template.Must(template.New("request-log").Parse(requestLoggingTpl)),
}
} }
func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@ -83,14 +111,13 @@ func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if !h.enabled { if !h.enabled {
return return
} }
logLine := buildLogLine(logger.authInfo, logger.upstream, req, url, t, logger.Status(), logger.Size()) h.writeLogLine(logger.authInfo, logger.upstream, req, url, t, logger.Status(), logger.Size())
h.writer.Write(logLine)
} }
// Log entry for req similar to Apache Common Log Format. // Log entry for req similar to Apache Common Log Format.
// ts is the timestamp with which the entry should be logged. // ts is the timestamp with which the entry should be logged.
// status, size are used to provide the response HTTP status and size. // status, size are used to provide the response HTTP status and size.
func buildLogLine(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) []byte { func (h loggingHandler) writeLogLine(username, upstream string, req *http.Request, url url.URL, ts time.Time, status int, size int) {
if username == "" { if username == "" {
username = "-" username = "-"
} }
@ -114,19 +141,20 @@ func buildLogLine(username, upstream string, req *http.Request, url url.URL, ts
duration := float64(time.Now().Sub(ts)) / float64(time.Second) duration := float64(time.Now().Sub(ts)) / float64(time.Second)
logLine := fmt.Sprintf("%s - %s [%s] %s %s %s %q %s %q %d %d %0.3f\n", h.logTemplate.Execute(h.writer, logMessageData{
client, Client: client,
username, Host: req.Host,
ts.Format("02/Jan/2006:15:04:05 -0700"), Protocol: req.Proto,
req.Host, RequestDuration: fmt.Sprintf("%0.3f", duration),
req.Method, RequestMethod: req.Method,
upstream, RequestURI: fmt.Sprintf("%q", url.RequestURI()),
url.RequestURI(), ResponseSize: fmt.Sprintf("%d", size),
req.Proto, StatusCode: fmt.Sprintf("%d", status),
req.UserAgent(), Timestamp: ts.Format("02/Jan/2006:15:04:05 -0700"),
status, Upstream: upstream,
size, UserAgent: fmt.Sprintf("%q", req.UserAgent()),
duration, Username: username,
) })
return []byte(logLine)
h.writer.Write([]byte("\n"))
} }

42
logging_handler_test.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestLoggingHandler_ServeHTTP(t *testing.T) {
ts := time.Now()
tests := []struct {
Format,
ExpectedLogMessage string
}{
{defaultRequestLoggingFormat, fmt.Sprintf("127.0.0.1 - - [%s] test-server GET - \"/foo/bar\" HTTP/1.1 \"\" 200 4 0.000\n", ts.Format("02/Jan/2006:15:04:05 -0700"))},
{"{{.RequestMethod}}", "GET\n"},
}
for _, test := range tests {
buf := bytes.NewBuffer(nil)
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := LoggingHandler(buf, http.HandlerFunc(handler), true, test.Format)
r, _ := http.NewRequest("GET", "/foo/bar", nil)
r.RemoteAddr = "127.0.0.1"
r.Host = "test-server"
h.ServeHTTP(httptest.NewRecorder(), r)
actual := buf.String()
if actual != test.ExpectedLogMessage {
t.Errorf("Log message was\n%s\ninstead of expected \n%s", actual, test.ExpectedLogMessage)
}
}
}

View File

@ -20,6 +20,7 @@ func main() {
emailDomains := StringArray{} emailDomains := StringArray{}
upstreams := StringArray{} upstreams := StringArray{}
skipAuthRegex := StringArray{} skipAuthRegex := StringArray{}
googleGroups := StringArray{}
permittedGroups := StringArray{} permittedGroups := StringArray{}
config := flagSet.String("config", "", "path to config file") config := flagSet.String("config", "", "path to config file")
@ -50,6 +51,7 @@ func main() {
flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.") flagSet.String("azure-tenant", "common", "go to a tenant-specific or common (tenant-independent) endpoint.")
flagSet.String("github-org", "", "restrict logins to members of this organisation") flagSet.String("github-org", "", "restrict logins to members of this organisation")
flagSet.String("github-team", "", "restrict logins to members of this team") flagSet.String("github-team", "", "restrict logins to members of this team")
flagSet.Var(&googleGroups, "google-group", "restrict logins to members of this google group (may be given multiple times).")
flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls") flagSet.String("google-admin-email", "", "the google admin to impersonate for api calls")
flagSet.String("google-service-account-json", "", "the path to the service account json credentials") flagSet.String("google-service-account-json", "", "the path to the service account json credentials")
flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"") flagSet.String("client-id", "", "the OAuth Client ID: ie: \"123456.apps.googleusercontent.com\"")
@ -70,6 +72,7 @@ func main() {
flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag") flagSet.Bool("cookie-httponly", true, "set HttpOnly cookie flag")
flagSet.Bool("request-logging", true, "Log requests to stdout") flagSet.Bool("request-logging", true, "Log requests to stdout")
flagSet.String("request-logging-format", defaultRequestLoggingFormat, "Template for log lines")
flagSet.String("provider", "google", "OAuth provider") flagSet.String("provider", "google", "OAuth provider")
flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)") flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
@ -128,7 +131,7 @@ func main() {
} }
s := &Server{ s := &Server{
Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging), Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging, opts.RequestLoggingFormat),
Opts: opts, Opts: opts,
} }
s.ListenAndServe() s.ListenAndServe()

View File

@ -525,14 +525,9 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
} }
func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) { func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
log.Printf("[OAuthCallback] Starting OAuthCallback")
remoteAddr := getRemoteAddr(req) remoteAddr := getRemoteAddr(req)
log.Printf("[OAuthCallback] remoteAddr = %s", remoteAddr)
// finish the oauth cycle // finish the oauth cycle
log.Printf("[OAuthCallback] req.ParseForm")
err := req.ParseForm() err := req.ParseForm()
if err != nil { if err != nil {
p.ErrorPage(rw, 500, "Internal Error", err.Error()) p.ErrorPage(rw, 500, "Internal Error", err.Error())
@ -541,8 +536,6 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
errorString := req.Form.Get("error") errorString := req.Form.Get("error")
if errorString != "" { if errorString != "" {
log.Printf("[OAuthCallback] error in parsed form (REQ.Form) : %s", req.Form)
log.Printf("[OAuthCallback] error in parsed form (REQ.error string) : %s", errorString)
p.ErrorPage(rw, 403, "Permission Denied", errorString) p.ErrorPage(rw, 403, "Permission Denied", errorString)
return return
} }

View File

@ -34,6 +34,7 @@ type Options struct {
EmailDomains []string `flag:"email-domain" cfg:"email_domains"` EmailDomains []string `flag:"email-domain" cfg:"email_domains"`
GitHubOrg string `flag:"github-org" cfg:"github_org"` GitHubOrg string `flag:"github-org" cfg:"github_org"`
GitHubTeam string `flag:"github-team" cfg:"github_team"` GitHubTeam string `flag:"github-team" cfg:"github_team"`
GoogleGroups []string `flag:"google-group" cfg:"google_group"`
GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email"` GoogleAdminEmail string `flag:"google-admin-email" cfg:"google_admin_email"`
GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json"` GoogleServiceAccountJSON string `flag:"google-service-account-json" cfg:"google_service_account_json"`
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"` HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
@ -77,7 +78,8 @@ type Options struct {
Scope string `flag:"scope" cfg:"scope"` Scope string `flag:"scope" cfg:"scope"`
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"`
RequestLogging bool `flag:"request-logging" cfg:"request_logging"` RequestLogging bool `flag:"request-logging" cfg:"request_logging"`
RequestLoggingFormat string `flag:"request-logging-format" cfg:"request_logging_format"`
SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"` SignatureKey string `flag:"signature-key" cfg:"signature_key" env:"OAUTH2_PROXY_SIGNATURE_KEY"`
@ -97,27 +99,28 @@ type SignatureData struct {
func NewOptions() *Options { func NewOptions() *Options {
return &Options{ return &Options{
ProxyPrefix: "/oauth2", ProxyPrefix: "/oauth2",
HttpAddress: "127.0.0.1:4180", HttpAddress: "127.0.0.1:4180",
HttpsAddress: ":443", HttpsAddress: ":443",
DisplayHtpasswdForm: true, Provider: "google",
CookieName: "_oauth2_proxy", DisplayHtpasswdForm: true,
CookieSecure: true, CookieName: "_oauth2_proxy",
CookieHttpOnly: true, CookieSecure: true,
CookieExpire: time.Duration(168) * time.Hour, CookieHttpOnly: true,
CookieRefresh: time.Duration(0), CookieExpire: time.Duration(168) * time.Hour,
SetXAuthRequest: false, CookieRefresh: time.Duration(0),
SkipAuthPreflight: false, SetXAuthRequest: false,
PassBasicAuth: true, SkipAuthPreflight: false,
PassUserHeaders: true, PassBasicAuth: true,
PassGroups: false, PassUserHeaders: true,
FilterGroups: "", PassGroups: false,
GroupsDelimiter: "|", FilterGroups: "",
PassAccessToken: false, GroupsDelimiter: "|",
PassHostHeader: true, PassAccessToken: false,
ApprovalPrompt: "", PassHostHeader: true,
RequestLogging: true, ApprovalPrompt: "force",
Provider: "google", RequestLogging: true,
RequestLoggingFormat: defaultRequestLoggingFormat,
} }
} }
@ -227,6 +230,18 @@ func (o *Options) Validate() error {
o.CookieExpire.String())) o.CookieExpire.String()))
} }
// Backwards compatibility. We can still use `GoogleGroups` if google is used as provider
if len(o.GoogleGroups) > 0 {
if o.Provider != "google" {
msgs = append(msgs, "incorrect setting: 'google-group' parameter could be used within google provider only")
}
if len(o.PermitGroups) > 0 {
msgs = append(msgs, "incorrect setting: 'google-group' and 'permit-groups' can't be defined together")
} else {
o.PermitGroups = o.GoogleGroups
}
}
if o.Provider == "google" { if o.Provider == "google" {
if len(o.PermitGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" { if len(o.PermitGroups) > 0 || o.GoogleAdminEmail != "" || o.GoogleServiceAccountJSON != "" {
if len(o.PermitGroups) < 1 { if len(o.PermitGroups) < 1 {

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strconv"
"strings" "strings"
) )
@ -61,36 +62,51 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
Login string `json:"login"` Login string `json:"login"`
} }
params := url.Values{ type orgsPage []struct {
"limit": {"100"}, Login string `json:"login"`
} }
endpoint := &url.URL{ pn := 1
Scheme: p.ValidateURL.Scheme, for {
Host: p.ValidateURL.Host, params := url.Values{
Path: path.Join(p.ValidateURL.Path, "/user/orgs"), "limit": {"200"},
RawQuery: params.Encode(), "page": {strconv.Itoa(pn)},
} }
req, _ := http.NewRequest("GET", endpoint.String(), nil)
req.Header.Set("Accept", "application/vnd.github.v3+json")
req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, err
}
body, err := ioutil.ReadAll(resp.Body) endpoint := &url.URL{
resp.Body.Close() Scheme: p.ValidateURL.Scheme,
if err != nil { Host: p.ValidateURL.Host,
return false, err Path: path.Join(p.ValidateURL.Path, "/user/orgs"),
} RawQuery: params.Encode(),
if resp.StatusCode != 200 { }
return false, fmt.Errorf( req, _ := http.NewRequest("GET", endpoint.String(), nil)
"got %d from %q %s", resp.StatusCode, endpoint.String(), body) req.Header.Set("Accept", "application/vnd.github.v3+json")
} req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false, err
}
if err := json.Unmarshal(body, &orgs); err != nil { body, err := ioutil.ReadAll(resp.Body)
return false, err resp.Body.Close()
if err != nil {
return false, err
}
if resp.StatusCode != 200 {
return false, fmt.Errorf(
"got %d from %q %s", resp.StatusCode, endpoint.String(), body)
}
var op orgsPage
if err := json.Unmarshal(body, &op); err != nil {
return false, err
}
if len(op) == 0 {
break
}
orgs = append(orgs, op...)
pn += 1
} }
var presentOrgs []string var presentOrgs []string
@ -118,7 +134,7 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
} }
params := url.Values{ params := url.Values{
"limit": {"100"}, "limit": {"200"},
} }
endpoint := &url.URL{ endpoint := &url.URL{

View File

@ -27,23 +27,32 @@ func testGitHubProvider(hostname string) *GitHubProvider {
return p return p
} }
func testGitHubBackend(payload string) *httptest.Server { func testGitHubBackend(payload []string) *httptest.Server {
pathToQueryMap := map[string]string{ pathToQueryMap := map[string][]string{
"/user": "", "/user": []string{""},
"/user/emails": "", "/user/emails": []string{""},
"/user/orgs": []string{"limit=200&page=1", "limit=200&page=2", "limit=200&page=3"},
} }
return httptest.NewServer(http.HandlerFunc( return httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
url := r.URL url := r.URL
query, ok := pathToQueryMap[url.Path] query, ok := pathToQueryMap[url.Path]
validQuery := false
index := 0
for i, q := range query {
if q == url.RawQuery {
validQuery = true
index = i
}
}
if !ok { if !ok {
w.WriteHeader(404) w.WriteHeader(404)
} else if url.RawQuery != query { } else if !validQuery {
w.WriteHeader(404) w.WriteHeader(404)
} else { } else {
w.WriteHeader(200) w.WriteHeader(200)
w.Write([]byte(payload)) w.Write([]byte(payload[index]))
} }
})) }))
} }
@ -89,7 +98,7 @@ func TestGitHubProviderOverrides(t *testing.T) {
} }
func TestGitHubProviderGetEmailAddress(t *testing.T) { func TestGitHubProviderGetEmailAddress(t *testing.T) {
b := testGitHubBackend(`[ {"email": "michael.bland@gsa.gov", "primary": true} ]`) b := testGitHubBackend([]string{`[ {"email": "michael.bland@gsa.gov", "primary": true} ]`})
defer b.Close() defer b.Close()
bURL, _ := url.Parse(b.URL) bURL, _ := url.Parse(b.URL)
@ -101,10 +110,28 @@ func TestGitHubProviderGetEmailAddress(t *testing.T) {
assert.Equal(t, "michael.bland@gsa.gov", email) assert.Equal(t, "michael.bland@gsa.gov", email)
} }
func TestGitHubProviderGetEmailAddressWithOrg(t *testing.T) {
b := testGitHubBackend([]string{
`[ {"email": "michael.bland@gsa.gov", "primary": true, "login":"testorg"} ]`,
`[ {"email": "michael.bland1@gsa.gov", "primary": true, "login":"testorg1"} ]`,
`[ ]`,
})
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testGitHubProvider(bURL.Host)
p.Org = "testorg1"
session := &SessionState{AccessToken: "imaginary_access_token"}
email, err := p.GetEmailAddress(session)
assert.Equal(t, nil, err)
assert.Equal(t, "michael.bland@gsa.gov", email)
}
// Note that trying to trigger the "failed building request" case is not // Note that trying to trigger the "failed building request" case is not
// practical, since the only way it can fail is if the URL fails to parse. // practical, since the only way it can fail is if the URL fails to parse.
func TestGitHubProviderGetEmailAddressFailedRequest(t *testing.T) { func TestGitHubProviderGetEmailAddressFailedRequest(t *testing.T) {
b := testGitHubBackend("unused payload") b := testGitHubBackend([]string{"unused payload"})
defer b.Close() defer b.Close()
bURL, _ := url.Parse(b.URL) bURL, _ := url.Parse(b.URL)
@ -120,7 +147,7 @@ func TestGitHubProviderGetEmailAddressFailedRequest(t *testing.T) {
} }
func TestGitHubProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) { func TestGitHubProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
b := testGitHubBackend("{\"foo\": \"bar\"}") b := testGitHubBackend([]string{"{\"foo\": \"bar\"}"})
defer b.Close() defer b.Close()
bURL, _ := url.Parse(b.URL) bURL, _ := url.Parse(b.URL)
@ -133,7 +160,7 @@ func TestGitHubProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
} }
func TestGitHubProviderGetUserName(t *testing.T) { func TestGitHubProviderGetUserName(t *testing.T) {
b := testGitHubBackend(`{"email": "michael.bland@gsa.gov", "login": "mbland"}`) b := testGitHubBackend([]string{`{"email": "michael.bland@gsa.gov", "login": "mbland"}`})
defer b.Close() defer b.Close()
bURL, _ := url.Parse(b.URL) bURL, _ := url.Parse(b.URL)