git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #7 from pusher/migration

Migration from Bitly to Pusher

commite1f45dd94166df8b047c21f92187918a19fb6806 authorJoel Speed <Joel.speed@hotmail.co.uk> dateMon 14 Jan 2019, 09:54 (8 years ago) parenta94b0a8f80ce24
55 files changed+958−444
.github/CODEOWNERS+3 .github/ISSUE_TEMPLATE.md+37 .github/PULL_REQUEST_TEMPLATE.md+25 .gitignore+1−1 .travis.yml+8−4 CHANGELOG.md+23 CONTRIBUTING.md+22 Dockerfile+16 Gopkg.lock+58−9 Gopkg.toml+1−5 Makefile+56 README.md+80−66 api/api.go+4−1 api/api_test.go+4−3 configure+137 cookie/cookies_test.go+3−2 cookie/nonce.go+1 env_options.go+6 htpasswd.go+9−5 htpasswd_test.go+1 http.go+10−6 logging_handler.go+8 main.go+1−1 oauthproxy.go+72−18 oauthproxy_test.go+148−150 options.go+23−17 options_test.go+2−2 providers/azure.go+9−4 providers/azure_test.go+1−2 providers/facebook.go+8−4 providers/github.go+8−2 providers/github_test.go+5−6 providers/gitlab.go+4−1 providers/gitlab_test.go+7−8 providers/google.go+8−3 providers/google_test.go+5−5 providers/internal_util.go+4−9 providers/internal_util_test.go+47−42 providers/linkedin.go+7−3 providers/linkedin_test.go+7−8 providers/oidc.go+8 providers/provider_data.go+3 providers/provider_default.go+6−2 providers/providers.go+3−1 providers/session_state.go+7−1 providers/session_state_test.go+1−1 string_array.go+3 templates.go+1−1 test.sh−14 validator.go+11−5 validator_test.go+13−13 validator_watcher_copy_test.go+4−4 validator_watcher_test.go+9−9 version.go+2−1 watcher.go+8−5
.github/CODEOWNERSnew file+3
@@ -0,0 +1,3 @@
1+# Default owner should be a Pusher cloud-team member unless overridden by later
2+# rules in this file
3+* @pusher/cloud-team
.github/ISSUE_TEMPLATE.mdnew file+37
@@ -0,0 +1,37 @@
1+<!--- Provide a general summary of the issue in the Title above -->
2+
3+## Expected Behavior
4+
5+<!--- If you're describing a bug, tell us what should happen -->
6+<!--- If you're suggesting a change/improvement, tell us how it should work -->
7+
8+## Current Behavior
9+
10+<!--- If describing a bug, tell us what happens instead of the expected behavior -->
11+<!--- If suggesting a change/improvement, explain the difference from current behavior -->
12+
13+## Possible Solution
14+
15+<!--- Not obligatory, but suggest a fix/reason for the bug, -->
16+<!--- or ideas how to implement the addition or change -->
17+
18+## Steps to Reproduce (for bugs)
19+
20+<!--- Provide a link to a live example, or an unambiguous set of steps to -->
21+<!--- reproduce this bug. Include code to reproduce, if relevant -->
22+
23+1. <!--- Step 1 --->
24+2. <!--- Step 2 --->
25+3. <!--- Step 3 --->
26+4. <!--- Step 4 --->
27+
28+## Context
29+
30+<!--- How has this issue affected you? What are you trying to accomplish? -->
31+<!--- Providing context helps us come up with a solution that is most useful in the real world -->
32+
33+## Your Environment
34+
35+<!--- Include as many relevant details about the environment you experienced the bug in -->
36+
37+- Version used:
.github/PULL_REQUEST_TEMPLATE.mdnew file+25
@@ -0,0 +1,25 @@
1+<!--- Provide a general summary of your changes in the Title above -->
2+
3+## Description
4+
5+<!--- Describe your changes in detail -->
6+
7+## Motivation and Context
8+
9+<!--- Why is this change required? What problem does it solve? -->
10+<!--- If it fixes an open issue, please link to the issue here. -->
11+
12+## How Has This Been Tested?
13+
14+<!--- Please describe in detail how you tested your changes. -->
15+<!--- Include details of your testing environment, and the tests you ran to -->
16+<!--- see how your change affects other areas of the code, etc. -->
17+
18+## Checklist:
19+
20+<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
21+<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
22+
23+- [ ] My change requires a change to the documentation or CHANGELOG.
24+- [ ] I have updated the documentation/CHANGELOG accordingly.
25+- [ ] I have created a feature (non-master) branch for my PR.
.gitignore+1−1
@@ -3,7 +3,7 @@ vendor
33dist
44.godeps
55*.exe
6−
6+.env
77
88# Go.gitignore
99# Compiled Object files, Static and Dynamic libs (Shared Objects)
.travis.yml+8−4
@@ -1,12 +1,16 @@
11language: go
22go:
3− - 1.8.x
43 - 1.9.x
5−script:
4+ - 1.10.x
5+install:
6+ # Fetch dependencies
67 - wget -O dep https://github.com/golang/dep/releases/download/v0.3.2/dep-linux-amd64
78 - chmod +x dep
8− - ./dep ensure
9− - ./test.sh
9+ - mv dep $GOPATH/bin/dep
10+script:
11+ - ./configure
12+ # Run tests
13+ - make test
1014sudo: false
1115notifications:
1216 email: false
CHANGELOG.mdnew file+23
@@ -0,0 +1,23 @@
1+# Vx.x.x (Pre-release)
2+
3+## Changes since v2.2:
4+
5+- Move automated build to debian base image
6+- Add Makefile
7+ - Update CI to run `make test`
8+ - Update Dockerfile to use `make clean oauth2_proxy`
9+ - Update `VERSION` parameter to be set by `ldflags` from Git Status
10+ - Remove lint and test scripts
11+- Remove Go v1.8.x from Travis CI testing
12+- Add CODEOWNERS file
13+- Add CONTRIBUTING guide
14+- Add Issue and Pull Request templates
15+- Add Dockerfile
16+- Fix fsnotify import
17+- Update README to reflect new repository ownership
18+- Update CI scripts to separate linting and testing
19+ - Now using `gometalinter` for linting
20+- Move Go import path from `github.com/bitly/oauth2_proxy` to `github.com/pusher/oauth2_proxy`
21+- Repository forked on 27/11/18
22+ - README updated to include note that this repository is forked
23+ - CHANGLOG created to track changes to repository from original fork
CONTRIBUTING.mdnew file+22
@@ -0,0 +1,22 @@
1+# Contributing
2+
3+To develop on this project, please fork the repo and clone into your `$GOPATH`.
4+
5+Dependencies are **not** checked in so please download those separately.
6+Download the dependencies using [`dep`](https://github.com/golang/dep).
7+
8+```bash
9+cd $GOPATH/src/github.com # Create this directory if it doesn't exist
10+git clone git@github.com:<YOUR_FORK>/oauth2_proxy pusher/oauth2_proxy
11+make dep
12+```
13+
14+## Pull Requests and Issues
15+
16+We track bugs and issues using Github.
17+
18+If you find a bug, please open an Issue.
19+
20+If you want to fix a bug, please fork, create a feature branch, fix the bug and
21+open a PR back to this repo.
22+Please mention the open bug issue number within your PR if applicable.
Dockerfilenew file+16
@@ -0,0 +1,16 @@
1+FROM golang:1.10 AS builder
2+WORKDIR /go/src/github.com/pusher/oauth2_proxy
3+COPY . .
4+
5+# Fetch dependencies
6+RUN go get -u github.com/golang/dep/cmd/dep
7+RUN dep ensure --vendor-only
8+
9+# Build image
10+RUN ./configure && make clean oauth2_proxy
11+
12+# Copy binary to debian
13+FROM debian:stretch
14+COPY --from=builder /go/src/github.com/pusher/oauth2_proxy/oauth2_proxy /bin/oauth2_proxy
15+
16+ENTRYPOINT ["/bin/oauth2_proxy"]
Gopkg.lock+58−9
@@ -2,118 +2,149 @@
22
33
44[[projects]]
5+ digest = "1:b24249f5a5e6fbe1eddc94b25973172339ccabeadef4779274f3ed0167c18812"
56 name = "cloud.google.com/go"
67 packages = ["compute/metadata"]
8+ pruneopts = ""
79 revision = "2d3a6656c17a60b0815b7e06ab0be04eacb6e613"
810 version = "v0.16.0"
911
1012[[projects]]
13+ digest = "1:289dd4d7abfb3ad2b5f728fbe9b1d5c1bf7d265a3eb9ef92869af1f7baba4c7a"
1114 name = "github.com/BurntSushi/toml"
1215 packages = ["."]
16+ pruneopts = ""
1317 revision = "b26d9c308763d68093482582cea63d69be07a0f0"
1418 version = "v0.3.0"
1519
1620[[projects]]
21+ digest = "1:512883404c2a99156e410e9880e3bb35ecccc0c07c1159eb204b5f3ef3c431b3"
1722 name = "github.com/bitly/go-simplejson"
1823 packages = ["."]
24+ pruneopts = ""
1925 revision = "aabad6e819789e569bd6aabf444c935aa9ba1e44"
2026 version = "v0.5.0"
2127
2228[[projects]]
2329 branch = "v2"
30+ digest = "1:e5a238f8fa890e529d7e493849bbae8988c9e70344e4630cc4f9a11b00516afb"
2431 name = "github.com/coreos/go-oidc"
2532 packages = ["."]
33+ pruneopts = ""
2634 revision = "77e7f2010a464ade7338597afe650dfcffbe2ca8"
2735
2836[[projects]]
37+ digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
2938 name = "github.com/davecgh/go-spew"
3039 packages = ["spew"]
40+ pruneopts = ""
3141 revision = "346938d642f2ec3594ed81d874461961cd0faa76"
3242 version = "v1.1.0"
3343
3444[[projects]]
3545 branch = "master"
46+ digest = "1:3b760d3b93f994df8eb1d9ebfad17d3e9e37edcb7f7efaa15b427c0d7a64f4e4"
3647 name = "github.com/golang/protobuf"
3748 packages = ["proto"]
49+ pruneopts = ""
3850 revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
3951
4052[[projects]]
53+ digest = "1:af67386ca553c04c6222f7b5b2f17bc97a5dfb3b81b706882c7fd8c72c30cf8f"
4154 name = "github.com/mbland/hmacauth"
4255 packages = ["."]
56+ pruneopts = ""
4357 revision = "107c17adcc5eccc9935cd67d9bc2feaf5255d2cb"
4458 version = "1.0.2"
4559
4660[[projects]]
4761 branch = "master"
62+ digest = "1:9408fb9c637c103010e5147469c232ce6b68edc840879cc730a2a15918e6cae8"
4863 name = "github.com/mreiferson/go-options"
4964 packages = ["."]
65+ pruneopts = ""
5066 revision = "77551d20752b54535462404ad9d877ebdb26e53d"
5167
5268[[projects]]
69+ digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411"
5370 name = "github.com/pmezard/go-difflib"
5471 packages = ["difflib"]
72+ pruneopts = ""
5573 revision = "792786c7400a136282c1664665ae0a8db921c6c2"
5674 version = "v1.0.0"
5775
5876[[projects]]
5977 branch = "master"
78+ digest = "1:386e12afcfd8964907c92dffd106860c0dedd71dbefae14397b77b724a13343b"
6079 name = "github.com/pquerna/cachecontrol"
6180 packages = [
6281 ".",
63− "cacheobject"
82+ "cacheobject",
6483 ]
84+ pruneopts = ""
6585 revision = "0dec1b30a0215bb68605dfc568e8855066c9202d"
6686
6787[[projects]]
88+ digest = "1:3926a4ec9a4ff1a072458451aa2d9b98acd059a45b38f7335d31e06c3d6a0159"
6889 name = "github.com/stretchr/testify"
6990 packages = ["assert"]
91+ pruneopts = ""
7092 revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
7193 version = "v1.1.4"
7294
7395[[projects]]
7496 branch = "master"
97+ digest = "1:f6a006d27619a4d93bf9b66fe1999b8c8d1fa62bdc63af14f10fbe6fcaa2aa1a"
7598 name = "golang.org/x/crypto"
7699 packages = [
77100 "bcrypt",
78101 "blowfish",
79102 "ed25519",
80− "ed25519/internal/edwards25519"
103+ "ed25519/internal/edwards25519",
81104 ]
105+ pruneopts = ""
82106 revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
83107
84108[[projects]]
85109 branch = "master"
110+ digest = "1:130b1bec86c62e121967ee0c69d9c263dc2d3ffe6c7c9a82aca4071c4d068861"
86111 name = "golang.org/x/net"
87112 packages = [
88113 "context",
89− "context/ctxhttp"
114+ "context/ctxhttp",
90115 ]
116+ pruneopts = ""
91117 revision = "9dfe39835686865bff950a07b394c12a98ddc811"
92118
93119[[projects]]
94120 branch = "master"
121+ digest = "1:4a61176e8386727e4847b21a5a2625ce56b9c518bc543a28226503e701265db0"
95122 name = "golang.org/x/oauth2"
96123 packages = [
97124 ".",
98125 "google",
99126 "internal",
100127 "jws",
101− "jwt"
128+ "jwt",
102129 ]
130+ pruneopts = ""
103131 revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402"
104132
105133[[projects]]
106134 branch = "master"
135+ digest = "1:dc1fb726dbbe79c86369941eae1e3b431b8fc6f11dbd37f7899dc758a43cc3ed"
107136 name = "google.golang.org/api"
108137 packages = [
109138 "admin/directory/v1",
110139 "gensupport",
111140 "googleapi",
112− "googleapi/internal/uritemplates"
141+ "googleapi/internal/uritemplates",
113142 ]
143+ pruneopts = ""
114144 revision = "8791354e7ab150705ede13637a18c1fcc16b62e8"
115145
116146[[projects]]
147+ digest = "1:934fb8966f303ede63aa405e2c8d7f0a427a05ea8df335dfdc1833dd4d40756f"
117148 name = "google.golang.org/appengine"
118149 packages = [
119150 ".",
@@ -125,30 +156,48 @@
125156 "internal/modules",
126157 "internal/remote_api",
127158 "internal/urlfetch",
128− "urlfetch"
159+ "urlfetch",
129160 ]
161+ pruneopts = ""
130162 revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
131163 version = "v1.0.0"
132164
133165[[projects]]
134− name = "gopkg.in/fsnotify.v1"
166+ digest = "1:cb5b2a45a3dd41c01ff779c54ae4c8aab0271d6d3b3f734c8a8bd2c890299ef2"
167+ name = "gopkg.in/fsnotify/fsnotify.v1"
135168 packages = ["."]
169+ pruneopts = ""
136170 revision = "836bfd95fecc0f1511dd66bdbf2b5b61ab8b00b6"
137171 version = "v1.2.11"
138172
139173[[projects]]
174+ digest = "1:be4ed0a2b15944dd777a663681a39260ed05f9c4e213017ed2e2255622c8820c"
140175 name = "gopkg.in/square/go-jose.v2"
141176 packages = [
142177 ".",
143178 "cipher",
144− "json"
179+ "json",
145180 ]
181+ pruneopts = ""
146182 revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
147183 version = "v2.1.3"
148184
149185[solve-meta]
150186 analyzer-name = "dep"
151187 analyzer-version = 1
152− inputs-digest = "b502c41a61115d14d6379be26b0300f65d173bdad852f0170d387ebf2d7ec173"
188+ input-imports = [
189+ "github.com/BurntSushi/toml",
190+ "github.com/bitly/go-simplejson",
191+ "github.com/coreos/go-oidc",
192+ "github.com/mbland/hmacauth",
193+ "github.com/mreiferson/go-options",
194+ "github.com/stretchr/testify/assert",
195+ "golang.org/x/crypto/bcrypt",
196+ "golang.org/x/oauth2",
197+ "golang.org/x/oauth2/google",
198+ "google.golang.org/api/admin/directory/v1",
199+ "google.golang.org/api/googleapi",
200+ "gopkg.in/fsnotify/fsnotify.v1",
201+ ]
153202 solver-name = "gps-cdcl"
154203 solver-version = 1
Gopkg.toml+1−5
@@ -3,10 +3,6 @@
33# for detailed Gopkg.toml documentation.
44#
55
6−[[constraint]]
7− name = "github.com/18F/hmacauth"
8− version = "~1.0.1"
9−
106[[constraint]]
117 name = "github.com/BurntSushi/toml"
128 version = "~0.3.0"
@@ -36,7 +32,7 @@
3632 name = "google.golang.org/api"
3733
3834[[constraint]]
39− name = "gopkg.in/fsnotify.v1"
35+ name = "gopkg.in/fsnotify/fsnotify.v1"
4036 version = "~1.2.0"
4137
4238[[constraint]]
Makefilenew file+56
@@ -0,0 +1,56 @@
1+include .env
2+BINARY := oauth2_proxy
3+VERSION := $(shell git describe --always --long --dirty --tags 2>/dev/null || echo "undefined")
4+.NOTPARALLEL:
5+
6+.PHONY: all
7+all: dep lint $(BINARY)
8+
9+.PHONY: clean
10+clean:
11+ rm -rf release
12+ rm -f $(BINARY)
13+
14+.PHONY: distclean
15+distclean: clean
16+ rm -rf vendor
17+
18+BIN_DIR := $(GOPATH)/bin
19+GOMETALINTER := $(BIN_DIR)/gometalinter
20+
21+$(GOMETALINTER):
22+ $(GO) get -u github.com/alecthomas/gometalinter
23+ gometalinter --install %> /dev/null
24+
25+.PHONY: lint
26+lint: $(GOMETALINTER)
27+ $(GOMETALINTER) --vendor --disable-all \
28+ --enable=vet \
29+ --enable=vetshadow \
30+ --enable=golint \
31+ --enable=ineffassign \
32+ --enable=goconst \
33+ --enable=deadcode \
34+ --enable=gofmt \
35+ --enable=goimports \
36+ --tests ./...
37+
38+.PHONY: dep
39+dep:
40+ $(DEP) ensure --vendor-only
41+
42+.PHONY: build
43+build: clean $(BINARY)
44+
45+$(BINARY):
46+ $(GO) build -ldflags="-X main.VERSION=${VERSION}" -o $(BINARY) github.com/pusher/oauth2_proxy
47+
48+.PHONY: test
49+test: dep lint
50+ $(GO) test -v -race $(go list ./... | grep -v /vendor/)
51+
52+.PHONY: release
53+release: lint test
54+ mkdir release
55+ GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-darwin-amd64 github.com/pusher/oauth2_proxy
56+ GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" -o release/$(BINARY)-linux-amd64 github.com/pusher/oauth2_proxy
README.md+80−66
@@ -1,11 +1,13 @@
1−oauth2_proxy
2−=================
1+# oauth2_proxy
32
43A reverse proxy and static file server that provides authentication using Providers (Google, GitHub, and others)
54to validate accounts by email, domain or group.
65
7−[![Build Status](https://secure.travis-ci.org/bitly/oauth2_proxy.svg?branch=master)](http://travis-ci.org/bitly/oauth2_proxy)
6+**Note:** This repository was forked from [bitly/OAuth2_Proxy](https://github.com/bitly/oauth2_proxy) on 27/11/2018.
7+Versions v3.0.0 and up are from this fork and will have diverged from any changes in the original fork.
8+A list of changes can be seen in the [CHANGELOG](CHANGELOG.md).
89
10+[![Build Status](https://secure.travis-ci.org/pusher/oauth2_proxy.svg?branch=master)](http://travis-ci.org/pusher/oauth2_proxy)
911
1012![Sign In Page](https://cloud.githubusercontent.com/assets/45028/4970624/7feb7dd8-6886-11e4-93e0-c9904af44ea8.png)
1113
@@ -15,15 +17,24 @@ to validate accounts by email, domain or group.
1517
1618## Installation
1719
18−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`
20+1. Choose how to deploy:
21+
22+ a. Download [Prebuilt Binary](https://github.com/pusher/oauth2_proxy/releases) (current release is `v2.2`)
23+
24+ b. Build with `$ go get github.com/pusher/oauth2_proxy` which will put the binary in `$GOROOT/bin`
25+
26+ c. Using the prebuilt docker image [quay.io/pusher/oauth2_proxy](https://quay.io/pusher/oauth2_proxy)
27+
1928Prebuilt 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`.
29+
2030```
2131sha256sum -c sha256sum.txt 2>&1 | grep OK
2232oauth2_proxy-2.3.linux-amd64: OK
2333```
24−2. Select a Provider and Register an OAuth Application with a Provider
25−3. Configure OAuth2 Proxy using config file, command line options, or environment variables
26−4. Configure SSL or Deploy behind a SSL endpoint (example provided for Nginx)
34+
35+2. Select a Provider and Register an OAuth Application with a Provider
36+3. Configure OAuth2 Proxy using config file, command line options, or environment variables
37+4. Configure SSL or Deploy behind a SSL endpoint (example provided for Nginx)
2738
2839## OAuth Provider Configuration
2940
@@ -31,12 +42,12 @@ You will need to register an OAuth application with a Provider (Google, GitHub o
3142
3243Valid providers are :
3344
34−* [Google](#google-auth-provider) *default*
35−* [Azure](#azure-auth-provider)
36−* [Facebook](#facebook-auth-provider)
37−* [GitHub](#github-auth-provider)
38−* [GitLab](#gitlab-auth-provider)
39−* [LinkedIn](#linkedin-auth-provider)
45+- [Google](#google-auth-provider) _default_
46+- [Azure](#azure-auth-provider)
47+- [Facebook](#facebook-auth-provider)
48+- [GitHub](#github-auth-provider)
49+- [GitLab](#gitlab-auth-provider)
50+- [LinkedIn](#linkedin-auth-provider)
4051
4152The provider can be selected using the `provider` configuration value.
4253
@@ -44,61 +55,62 @@ The provider can be selected using the `provider` configuration value.
4455
4556For Google, the registration steps are:
4657
47−1. Create a new project: https://console.developers.google.com/project
48−2. Choose the new project from the top right project dropdown (only if another project is selected)
49−3. In the project Dashboard center pane, choose **"API Manager"**
50−4. In the left Nav pane, choose **"Credentials"**
51−5. In the center pane, choose **"OAuth consent screen"** tab. Fill in **"Product name shown to users"** and hit save.
52−6. In the center pane, choose **"Credentials"** tab.
53− * Open the **"New credentials"** drop down
54− * Choose **"OAuth client ID"**
55− * Choose **"Web application"**
56− * Application name is freeform, choose something appropriate
57− * Authorized JavaScript origins is your domain ex: `https://internal.yourcompany.com`
58− * Authorized redirect URIs is the location of oauth2/callback ex: `https://internal.yourcompany.com/oauth2/callback`
59− * Choose **"Create"**
60−4. Take note of the **Client ID** and **Client Secret**
58+1. Create a new project: https://console.developers.google.com/project
59+2. Choose the new project from the top right project dropdown (only if another project is selected)
60+3. In the project Dashboard center pane, choose **"API Manager"**
61+4. In the left Nav pane, choose **"Credentials"**
62+5. In the center pane, choose **"OAuth consent screen"** tab. Fill in **"Product name shown to users"** and hit save.
63+6. In the center pane, choose **"Credentials"** tab.
64+ - Open the **"New credentials"** drop down
65+ - Choose **"OAuth client ID"**
66+ - Choose **"Web application"**
67+ - Application name is freeform, choose something appropriate
68+ - Authorized JavaScript origins is your domain ex: `https://internal.yourcompany.com`
69+ - Authorized redirect URIs is the location of oauth2/callback ex: `https://internal.yourcompany.com/oauth2/callback`
70+ - Choose **"Create"**
71+7. Take note of the **Client ID** and **Client Secret**
6172
6273It's recommended to refresh sessions on a short interval (1h) with `cookie-refresh` setting which validates that the account is still authorized.
6374
6475#### Restrict auth to specific Google groups on your domain. (optional)
6576
66−1. Create a service account: https://developers.google.com/identity/protocols/OAuth2ServiceAccount and make sure to download the json file.
67−2. Make note of the Client ID for a future step.
68−3. Under "APIs & Auth", choose APIs.
69−4. Click on Admin SDK and then Enable API.
70−5. Follow the steps on https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account and give the client id from step 2 the following oauth scopes:
77+1. Create a service account: https://developers.google.com/identity/protocols/OAuth2ServiceAccount and make sure to download the json file.
78+2. Make note of the Client ID for a future step.
79+3. Under "APIs & Auth", choose APIs.
80+4. Click on Admin SDK and then Enable API.
81+5. Follow the steps on https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account and give the client id from step 2 the following oauth scopes:
82+
7183```
7284https://www.googleapis.com/auth/admin.directory.group.readonly
7385https://www.googleapis.com/auth/admin.directory.user.readonly
7486```
75−6. Follow the steps on https://support.google.com/a/answer/60757 to enable Admin API access.
76−7. Create or choose an existing administrative email address on the Gmail domain to assign to the ```google-admin-email``` flag. This email will be impersonated by this client to make calls to the Admin SDK. See the note on the link from step 5 for the reason why.
77−8. Create or choose an existing email group and set that email to the ```google-group``` flag. You can pass multiple instances of this flag with different groups
78−and the user will be checked against all the provided groups.
79−9. Lock down the permissions on the json file downloaded from step 1 so only oauth2_proxy is able to read the file and set the path to the file in the ```google-service-account-json``` flag.
87+
88+6. Follow the steps on https://support.google.com/a/answer/60757 to enable Admin API access.
89+7. Create or choose an existing administrative email address on the Gmail domain to assign to the `google-admin-email` flag. This email will be impersonated by this client to make calls to the Admin SDK. See the note on the link from step 5 for the reason why.
90+8. Create or choose an existing email group and set that email to the `google-group` flag. You can pass multiple instances of this flag with different groups
91+ and the user will be checked against all the provided groups.
92+9. Lock down the permissions on the json file downloaded from step 1 so only oauth2_proxy is able to read the file and set the path to the file in the `google-service-account-json` flag.
809310. Restart oauth2_proxy.
8194
8295Note: The user is checked against the group members list on initial authentication and every time the token is refreshed ( about once an hour ).
8396
8497### Azure Auth Provider
8598
86−1. [Add an application](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/) to your Azure Active Directory tenant.
87−2. On the App properties page provide the correct Sign-On URL ie `https://internal.yourcompany.com/oauth2/callback`
88−3. If applicable take note of your `TenantID` and provide it via the `--azure-tenant=<YOUR TENANT ID>` commandline option. Default the `common` tenant is used.
99+1. [Add an application](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/) to your Azure Active Directory tenant.
100+2. On the App properties page provide the correct Sign-On URL ie `https://internal.yourcompany.com/oauth2/callback`
101+3. If applicable take note of your `TenantID` and provide it via the `--azure-tenant=<YOUR TENANT ID>` commandline option. Default the `common` tenant is used.
89102
90103The Azure AD auth provider uses `openid` as it default scope. It uses `https://graph.windows.net` as a default protected resource. It call to `https://graph.windows.net/me` to get the email address of the user that logs in.
91104
92−
93105### Facebook Auth Provider
94106
95−1. Create a new FB App from <https://developers.facebook.com/>
96−2. Under FB Login, set your Valid OAuth redirect URIs to `https://internal.yourcompany.com/oauth2/callback`
107+1. Create a new FB App from <https://developers.facebook.com/>
108+2. Under FB Login, set your Valid OAuth redirect URIs to `https://internal.yourcompany.com/oauth2/callback`
97109
98110### GitHub Auth Provider
99111
100−1. Create a new project: https://github.com/settings/developers
101−2. Under `Authorization callback URL` enter the correct url ie `https://internal.yourcompany.com/oauth2/callback`
112+1. Create a new project: https://github.com/settings/developers
113+2. Under `Authorization callback URL` enter the correct url ie `https://internal.yourcompany.com/oauth2/callback`
102114
103115The GitHub auth provider supports two additional parameters to restrict authentication to Organization or Team level access. Restricting by org and team is normally accompanied with `--email-domain=*`
104116
@@ -121,17 +133,16 @@ If you are using self-hosted GitLab, make sure you set the following to the appr
121133 -redeem-url="<your gitlab url>/oauth/token"
122134 -validate-url="<your gitlab url>/api/v4/user"
123135
124−
125136### LinkedIn Auth Provider
126137
127138For LinkedIn, the registration steps are:
128139
129−1. Create a new project: https://www.linkedin.com/secure/developer
130−2. In the OAuth User Agreement section:
131− * In default scope, select r_basicprofile and r_emailaddress.
132− * In "OAuth 2.0 Redirect URLs", enter `https://internal.yourcompany.com/oauth2/callback`
133−3. Fill in the remaining required fields and Save.
134−4. Take note of the **Consumer Key / API Key** and **Consumer Secret / Secret Key**
140+1. Create a new project: https://www.linkedin.com/secure/developer
141+2. In the OAuth User Agreement section:
142+ - In default scope, select r_basicprofile and r_emailaddress.
143+ - In "OAuth 2.0 Redirect URLs", enter `https://internal.yourcompany.com/oauth2/callback`
144+3. Fill in the remaining required fields and Save.
145+4. Take note of the **Consumer Key / API Key** and **Consumer Secret / Secret Key**
135146
136147### Microsoft Azure AD Provider
137148
@@ -143,9 +154,9 @@ Take note of your `TenantId` if applicable for your situation. The `TenantId` ca
143154
144155OpenID Connect is a spec for OAUTH 2.0 + identity that is implemented by many major providers and several open source projects. This provider was originally built against CoreOS Dex and we will use it as an example.
145156
146−1. Launch a Dex instance using the [getting started guide](https://github.com/coreos/dex/blob/master/Documentation/getting-started.md).
147−2. Setup oauth2_proxy with the correct provider and using the default ports and callbacks.
148−3. Login with the fixture use in the dex guide and run the oauth2_proxy with the following args:
157+1. Launch a Dex instance using the [getting started guide](https://github.com/coreos/dex/blob/master/Documentation/getting-started.md).
158+2. Setup oauth2_proxy with the correct provider and using the default ports and callbacks.
159+3. Login with the fixture use in the dex guide and run the oauth2_proxy with the following args:
149160
150161 -provider oidc
151162 -client-id oauth2_proxy
@@ -253,7 +264,7 @@ The following environment variables can be used in place of the corresponding co
253264
254265There are two recommended configurations.
255266
256−1) Configure SSL Termination with OAuth2 Proxy by providing a `--tls-cert=/path/to/cert.pem` and `--tls-key=/path/to/cert.key`.
267+1. Configure SSL Termination with OAuth2 Proxy by providing a `--tls-cert=/path/to/cert.pem` and `--tls-key=/path/to/cert.key`.
257268
258269The command line to run `oauth2_proxy` in this configuration would look like this:
259270
@@ -270,8 +281,7 @@ The command line to run `oauth2_proxy` in this configuration would look like thi
270281 --client-secret=...
271282```
272283
273−
274−2) Configure SSL Termination with [Nginx](http://nginx.org/) (example config below), Amazon ELB, Google Cloud Platform Load Balancing, or ....
284+2. Configure SSL Termination with [Nginx](http://nginx.org/) (example config below), Amazon ELB, Google Cloud Platform Load Balancing, or ....
275285
276286Because `oauth2_proxy` listens on `127.0.0.1:4180` by default, to listen on all interfaces (needed when using an
277287external load balancer like Amazon ELB or Google Platform Load Balancing) use `--http-address="0.0.0.0:4180"` or
@@ -321,12 +331,12 @@ The command line to run `oauth2_proxy` in this configuration would look like thi
321331
322332OAuth2 Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated. The `/oauth2` prefix can be changed with the `--proxy-prefix` config variable.
323333
324−* /robots.txt - returns a 200 OK response that disallows all User-agents from all paths; see [robotstxt.org](http://www.robotstxt.org/) for more info
325−* /ping - returns an 200 OK response
326−* /oauth2/sign_in - the login page, which also doubles as a sign out page (it clears cookies)
327−* /oauth2/start - a URL that will redirect to start the OAuth cycle
328−* /oauth2/callback - the URL used at the end of the OAuth cycle. The oauth app will be configured with this as the callback url.
329−* /oauth2/auth - only returns a 202 Accepted response or a 401 Unauthorized response; for use with the [Nginx `auth_request` directive](#nginx-auth-request)
334+- /robots.txt - returns a 200 OK response that disallows all User-agents from all paths; see [robotstxt.org](http://www.robotstxt.org/) for more info
335+- /ping - returns an 200 OK response
336+- /oauth2/sign_in - the login page, which also doubles as a sign out page (it clears cookies)
337+- /oauth2/start - a URL that will redirect to start the OAuth cycle
338+- /oauth2/callback - the URL used at the end of the OAuth cycle. The oauth app will be configured with this as the callback url.
339+- /oauth2/auth - only returns a 202 Accepted response or a 401 Unauthorized response; for use with the [Nginx `auth_request` directive](#nginx-auth-request)
330340
331341## Request signatures
332342
@@ -341,9 +351,9 @@ in `oauthproxy.go`](./oauthproxy.go).
341351For more information about HMAC request signature validation, read the
342352following:
343353
344−* [Amazon Web Services: Signing and Authenticating REST
354+- [Amazon Web Services: Signing and Authenticating REST
345355 Requests](https://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html)
346−* [rc3.org: Using HMAC to authenticate Web service
356+- [rc3.org: Using HMAC to authenticate Web service
347357 requests](http://rc3.org/2011/12/02/using-hmac-to-authenticate-web-service-requests/)
348358
349359## Logging Format
@@ -417,3 +427,7 @@ server {
417427 }
418428}
419429```
430+
431+## Contributing
432+
433+Please see our [Contributing](CONTRIBUTING.md) guidelines.
api/api.go+4−1
@@ -10,6 +10,7 @@ import (
1010 "github.com/bitly/go-simplejson"
1111)
1212
13+// Request parses the request body into a simplejson.Json object
1314func Request(req *http.Request) (*simplejson.Json, error) {
1415 resp, err := http.DefaultClient.Do(req)
1516 if err != nil {
@@ -32,7 +33,8 @@ func Request(req *http.Request) (*simplejson.Json, error) {
3233 return data, nil
3334}
3435
35−func RequestJson(req *http.Request, v interface{}) error {
36+// RequestJSON parses the request body into the given interface
37+func RequestJSON(req *http.Request, v interface{}) error {
3638 resp, err := http.DefaultClient.Do(req)
3739 if err != nil {
3840 log.Printf("%s %s %s", req.Method, req.URL, err)
@@ -50,6 +52,7 @@ func RequestJson(req *http.Request, v interface{}) error {
5052 return json.Unmarshal(body, v)
5153}
5254
55+// RequestUnparsedResponse performs a GET and returns the raw response object
5356func RequestUnparsedResponse(url string, header http.Header) (resp *http.Response, err error) {
5457 req, err := http.NewRequest("GET", url, nil)
5558 if err != nil {
api/api_test.go+4−3
@@ -1,20 +1,21 @@
11package api
22
33import (
4− "github.com/bitly/go-simplejson"
54 "io/ioutil"
65 "net/http"
76 "net/http/httptest"
87 "strings"
98 "testing"
109
10+ "github.com/bitly/go-simplejson"
11+
1112 "github.com/stretchr/testify/assert"
1213)
1314
14−func testBackend(response_code int, payload string) *httptest.Server {
15+func testBackend(responseCode int, payload string) *httptest.Server {
1516 return httptest.NewServer(http.HandlerFunc(
1617 func(w http.ResponseWriter, r *http.Request) {
17− w.WriteHeader(response_code)
18+ w.WriteHeader(responseCode)
1819 w.Write([]byte(payload))
1920 }))
2021}
configurenew file+137
@@ -0,0 +1,137 @@
1+#!/usr/bin/env bash
2+
3+RED='\033[0;31m'
4+GREEN='\033[0;32m'
5+BLUE='\033[0;34m'
6+NC='\033[0m'
7+
8+declare -A tools=()
9+declare -A desired=()
10+
11+for arg in "$@"; do
12+ case ${arg%%=*} in
13+ "--with-go")
14+ desired[go]="${arg##*=}"
15+ ;;
16+ "--with-dep")
17+ desired[dep]="${arg##*=}"
18+ ;;
19+ "--help")
20+ printf "${GREEN}$0${NC}\n"
21+ printf " available options:\n"
22+ printf " --with-dep=${BLUE}<path_to_dep_binary>${NC}\n"
23+ printf " --with-go=${BLUE}<path_to_go_binary>${NC}\n"
24+ exit 0
25+ ;;
26+ *)
27+ echo "Unknown option: $arg"
28+ exit 2
29+ ;;
30+ esac
31+done
32+
33+vercomp () {
34+ if [[ $1 == $2 ]]
35+ then
36+ return 0
37+ fi
38+ local IFS=.
39+ local i ver1=($1) ver2=($2)
40+ # fill empty fields in ver1 with zeros
41+ for ((i=${#ver1[@]}; i<${#ver2[@]}; i++))
42+ do
43+ ver1[i]=0
44+ done
45+ for ((i=0; i<${#ver1[@]}; i++))
46+ do
47+ if [[ -z ${ver2[i]} ]]
48+ then
49+ # fill empty fields in ver2 with zeros
50+ ver2[i]=0
51+ fi
52+ if ((10#${ver1[i]} > 10#${ver2[i]}))
53+ then
54+ return 1
55+ fi
56+ if ((10#${ver1[i]} < 10#${ver2[i]}))
57+ then
58+ return 2
59+ fi
60+ done
61+ return 0
62+}
63+
64+check_for() {
65+ echo -n "Checking for $1... "
66+ if ! [ -z "${desired[$1]}" ]; then
67+ TOOL_PATH="${desired[$1]}"
68+ else
69+ TOOL_PATH=$(command -v $1)
70+ fi
71+ if ! [ -x "$TOOL_PATH" -a -f "$TOOL_PATH" ]; then
72+ printf "${RED}not found${NC}\n"
73+ cd -
74+ exit 1
75+ else
76+ printf "${GREEN}found${NC}\n"
77+ tools[$1]=$TOOL_PATH
78+ fi
79+}
80+
81+check_go_version() {
82+ echo -n "Checking go version... "
83+ GO_VERSION=$(${tools[go]} version | ${tools[awk]} '{where = match($0, /[0-9]\.[0-9]+\.[0-9]*/); if (where != 0) print substr($0, RSTART, RLENGTH)}')
84+ vercomp $GO_VERSION 1.9
85+ case $? in
86+ 0) ;&
87+ 1)
88+ printf "${GREEN}"
89+ echo $GO_VERSION
90+ printf "${NC}"
91+ ;;
92+ 2)
93+ printf "${RED}"
94+ echo "$GO_VERSION < 1.9"
95+ exit 1
96+ ;;
97+ esac
98+}
99+
100+check_docker_version() {
101+ echo -n "Checking docker version... "
102+ DOCKER_VERSION=$(${tools[docker]} version | ${tools[awk]})
103+}
104+
105+check_go_env() {
106+ echo -n "Checking \$GOPATH... "
107+ if [ -z "$GOPATH" ]; then
108+ printf "${RED}invalid${NC} - GOPATH not set\n"
109+ exit 1
110+ fi
111+ printf "${GREEN}valid${NC} - $GOPATH\n"
112+}
113+
114+cd ${0%/*}
115+
116+if [ ! -f .env ]; then
117+ rm .env
118+fi
119+
120+check_for make
121+check_for awk
122+check_for go
123+check_go_version
124+check_go_env
125+check_for dep
126+
127+echo
128+
129+cat <<- EOF > .env
130+ MAKE := "${tools[make]}"
131+ GO := "${tools[go]}"
132+ DEP := "${tools[dep]}"
133+EOF
134+
135+echo "Environment configuration written to .env"
136+
137+cd - > /dev/null
cookie/cookies_test.go+3−2
@@ -24,10 +24,11 @@ func TestEncodeAndDecodeAccessToken(t *testing.T) {
2424}
2525
2626func TestEncodeAndDecodeAccessTokenB64(t *testing.T) {
27− const secret_b64 = "A3Xbr6fu6Al0HkgrP1ztjb-mYiwmxgNPP-XbNsz1WBk="
27+ const secretBase64 = "A3Xbr6fu6Al0HkgrP1ztjb-mYiwmxgNPP-XbNsz1WBk="
2828 const token = "my access token"
2929
30− secret, err := base64.URLEncoding.DecodeString(secret_b64)
30+ secret, err := base64.URLEncoding.DecodeString(secretBase64)
31+ assert.Equal(t, nil, err)
3132 c, err := NewCipher([]byte(secret))
3233 assert.Equal(t, nil, err)
3334
cookie/nonce.go+1
@@ -5,6 +5,7 @@ import (
55 "fmt"
66)
77
8+// Nonce generates a random 16 byte string to be used as a nonce
89func Nonce() (nonce string, err error) {
910 b := make([]byte, 16)
1011 _, err = rand.Read(b)
env_options.go+6
@@ -6,8 +6,14 @@ import (
66 "strings"
77)
88
9+// EnvOptions holds program options loaded from the process environment
910type EnvOptions map[string]interface{}
1011
12+// LoadEnvForStruct loads environment variables for each field in an options
13+// struct passed into it.
14+//
15+// Fields in the options struct must have an `env` and `cfg` tag to be read
16+// from the environment
1117func (cfg EnvOptions) LoadEnvForStruct(options interface{}) {
1218 val := reflect.ValueOf(options).Elem()
1319 typ := val.Type()
htpasswd.go+9−5
@@ -14,10 +14,12 @@ import (
1414// Lookup passwords in a htpasswd file
1515// Passwords must be generated with -B for bcrypt or -s for SHA1.
1616
17+// HtpasswdFile represents the structure of an htpasswd file
1718type HtpasswdFile struct {
1819 Users map[string]string
1920}
2021
22+// NewHtpasswdFromFile constructs an HtpasswdFile from the file at the path given
2123func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
2224 r, err := os.Open(path)
2325 if err != nil {
@@ -27,13 +29,14 @@ func NewHtpasswdFromFile(path string) (*HtpasswdFile, error) {
2729 return NewHtpasswd(r)
2830}
2931
32+// NewHtpasswd consctructs an HtpasswdFile from an io.Reader (opened file)
3033func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
31− csv_reader := csv.NewReader(file)
32− csv_reader.Comma = ':'
33− csv_reader.Comment = '#'
34− csv_reader.TrimLeadingSpace = true
34+ csvReader := csv.NewReader(file)
35+ csvReader.Comma = ':'
36+ csvReader.Comment = '#'
37+ csvReader.TrimLeadingSpace = true
3538
36− records, err := csv_reader.ReadAll()
39+ records, err := csvReader.ReadAll()
3740 if err != nil {
3841 return nil, err
3942 }
@@ -44,6 +47,7 @@ func NewHtpasswd(file io.Reader) (*HtpasswdFile, error) {
4447 return h, nil
4548}
4649
50+// Validate checks a users password against the HtpasswdFile entries
4751func (h *HtpasswdFile) Validate(user string, password string) bool {
4852 realPassword, exists := h.Users[user]
4953 if !exists {
htpasswd_test.go+1
@@ -20,6 +20,7 @@ func TestSHA(t *testing.T) {
2020
2121func TestBcrypt(t *testing.T) {
2222 hash1, err := bcrypt.GenerateFromPassword([]byte("password"), 1)
23+ assert.Equal(t, err, nil)
2324 hash2, err := bcrypt.GenerateFromPassword([]byte("top-secret"), 2)
2425 assert.Equal(t, err, nil)
2526
http.go+10−6
@@ -9,11 +9,13 @@ import (
99 "time"
1010)
1111
12+// Server represents an HTTP server
1213type Server struct {
1314 Handler http.Handler
1415 Opts *Options
1516}
1617
18+// ListenAndServe will serve traffic on HTTP or HTTPS depending on TLS options
1719func (s *Server) ListenAndServe() {
1820 if s.Opts.TLSKeyFile != "" || s.Opts.TLSCertFile != "" {
1921 s.ServeHTTPS()
@@ -22,13 +24,14 @@ func (s *Server) ListenAndServe() {
2224 }
2325}
2426
27+// ServeHTTP constructs a net.Listener and starts handling HTTP requests
2528func (s *Server) ServeHTTP() {
26− httpAddress := s.Opts.HttpAddress
27− scheme := ""
29+ HTTPAddress := s.Opts.HTTPAddress
30+ var scheme string
2831
29− i := strings.Index(httpAddress, "://")
32+ i := strings.Index(HTTPAddress, "://")
3033 if i > -1 {
31− scheme = httpAddress[0:i]
34+ scheme = HTTPAddress[0:i]
3235 }
3336
3437 var networkType string
@@ -39,7 +42,7 @@ func (s *Server) ServeHTTP() {
3942 networkType = scheme
4043 }
4144
42− slice := strings.SplitN(httpAddress, "//", 2)
45+ slice := strings.SplitN(HTTPAddress, "//", 2)
4346 listenAddr := slice[len(slice)-1]
4447
4548 listener, err := net.Listen(networkType, listenAddr)
@@ -57,8 +60,9 @@ func (s *Server) ServeHTTP() {
5760 log.Printf("HTTP: closing %s", listener.Addr())
5861}
5962
63+// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
6064func (s *Server) ServeHTTPS() {
61− addr := s.Opts.HttpsAddress
65+ addr := s.Opts.HTTPSAddress
6266 config := &tls.Config{
6367 MinVersion: tls.VersionTLS12,
6468 MaxVersion: tls.VersionTLS12,
logging_handler.go+8
@@ -27,10 +27,13 @@ type responseLogger struct {
2727 authInfo string
2828}
2929
30+// Header returns the ResponseWriter's Header
3031func (l *responseLogger) Header() http.Header {
3132 return l.w.Header()
3233}
3334
35+// ExtractGAPMetadata extracts and removes GAP headers from the ResponseWriter's
36+// Header
3437func (l *responseLogger) ExtractGAPMetadata() {
3538 upstream := l.w.Header().Get("GAP-Upstream-Address")
3639 if upstream != "" {
@@ -44,6 +47,7 @@ func (l *responseLogger) ExtractGAPMetadata() {
4447 }
4548}
4649
50+// Write writes the response using the ResponseWriter
4751func (l *responseLogger) Write(b []byte) (int, error) {
4852 if l.status == 0 {
4953 // The status will be StatusOK if WriteHeader has not been called yet
@@ -55,16 +59,19 @@ func (l *responseLogger) Write(b []byte) (int, error) {
5559 return size, err
5660}
5761
62+// WriteHeader writes the status code for the Response
5863func (l *responseLogger) WriteHeader(s int) {
5964 l.ExtractGAPMetadata()
6065 l.w.WriteHeader(s)
6166 l.status = s
6267}
6368
69+// Status returns the response status code
6470func (l *responseLogger) Status() int {
6571 return l.status
6672}
6773
74+// Size returns teh response size
6875func (l *responseLogger) Size() int {
6976 return l.size
7077}
@@ -94,6 +101,7 @@ type loggingHandler struct {
94101 logTemplate *template.Template
95102}
96103
104+// LoggingHandler provides an http.Handler which logs requests to the HTTP server
97105func LoggingHandler(out io.Writer, h http.Handler, v bool, requestLoggingTpl string) http.Handler {
98106 return loggingHandler{
99107 writer: out,
main.go+1−1
@@ -84,7 +84,7 @@ func main() {
8484 flagSet.Parse(os.Args[1:])
8585
8686 if *showVersion {
87− fmt.Printf("oauth2_proxy v%s (built with %s)\n", VERSION, runtime.Version())
87+ fmt.Printf("oauth2_proxy %s (built with %s)\n", VERSION, runtime.Version())
8888 return
8989 }
9090
oauthproxy.go+72−18
@@ -14,14 +14,23 @@ import (
1414 "strings"
1515 "time"
1616
17− "github.com/bitly/oauth2_proxy/cookie"
18− "github.com/bitly/oauth2_proxy/providers"
1917 "github.com/mbland/hmacauth"
18+ "github.com/pusher/oauth2_proxy/cookie"
19+ "github.com/pusher/oauth2_proxy/providers"
2020)
2121
22−const SignatureHeader = "GAP-Signature"
22+const (
23+ // SignatureHeader is the name of the request header containing the GAP Signature
24+ // Part of hmacauth
25+ SignatureHeader = "GAP-Signature"
2326
24−var SignatureHeaders []string = []string{
27+ httpScheme = "http"
28+ httpsScheme = "https"
29+)
30+
31+// SignatureHeaders contains the headers to be signed by the hmac algorithm
32+// Part of hmacauth
33+var SignatureHeaders = []string{
2534 "Content-Length",
2635 "Content-Md5",
2736 "Content-Type",
@@ -34,13 +43,14 @@ var SignatureHeaders []string = []string{
3443 "Gap-Auth",
3544}
3645
46+// OAuthProxy is the main authentication proxy
3747type OAuthProxy struct {
3848 CookieSeed string
3949 CookieName string
4050 CSRFCookieName string
4151 CookieDomain string
4252 CookieSecure bool
43− CookieHttpOnly bool
53+ CookieHTTPOnly bool
4454 CookieExpire time.Duration
4555 CookieRefresh time.Duration
4656 Validator func(string) bool
@@ -74,12 +84,15 @@ type OAuthProxy struct {
7484 Footer string
7585}
7686
87+// UpstreamProxy represents an upstream server to proxy to
7788type UpstreamProxy struct {
7889 upstream string
7990 handler http.Handler
8091 auth hmacauth.HmacAuth
8192}
8293
94+// ServeHTTP proxies requests to the upstream provider while signing the
95+// request headers
8396func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
8497 w.Header().Set("GAP-Upstream-Address", u.upstream)
8598 if u.auth != nil {
@@ -89,9 +102,12 @@ func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
89102 u.handler.ServeHTTP(w, r)
90103}
91104
105+// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
106+// servers
92107func NewReverseProxy(target *url.URL) (proxy *httputil.ReverseProxy) {
93108 return httputil.NewSingleHostReverseProxy(target)
94109}
110+
95111func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
96112 director := proxy.Director
97113 proxy.Director = func(req *http.Request) {
@@ -102,6 +118,7 @@ func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
102118 req.URL.RawQuery = ""
103119 }
104120}
121+
105122func setProxyDirector(proxy *httputil.ReverseProxy) {
106123 director := proxy.Director
107124 proxy.Director = func(req *http.Request) {
@@ -111,10 +128,13 @@ func setProxyDirector(proxy *httputil.ReverseProxy) {
111128 req.URL.RawQuery = ""
112129 }
113130}
131+
132+// NewFileServer creates a http.Handler to serve files from the filesystem
114133func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
115134 return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
116135}
117136
137+// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
118138func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
119139 serveMux := http.NewServeMux()
120140 var auth hmacauth.HmacAuth
@@ -125,7 +145,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
125145 for _, u := range opts.proxyURLs {
126146 path := u.Path
127147 switch u.Scheme {
128− case "http", "https":
148+ case httpScheme, httpsScheme:
129149 u.Path = ""
130150 log.Printf("mapping path %q => upstream %q", path, u)
131151 proxy := NewReverseProxy(u)
@@ -160,7 +180,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
160180 refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
161181 }
162182
163− log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, opts.CookieDomain, refresh)
183+ log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, refresh)
164184
165185 var cipher *cookie.Cipher
166186 if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
@@ -177,7 +197,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
177197 CookieSeed: opts.CookieSecret,
178198 CookieDomain: opts.CookieDomain,
179199 CookieSecure: opts.CookieSecure,
180− CookieHttpOnly: opts.CookieHttpOnly,
200+ CookieHTTPOnly: opts.CookieHTTPOnly,
181201 CookieExpire: opts.CookieExpire,
182202 CookieRefresh: opts.CookieRefresh,
183203 Validator: validator,
@@ -209,6 +229,8 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
209229 }
210230}
211231
232+// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
233+// redirect clients to once authenticated
212234func (p *OAuthProxy) GetRedirectURI(host string) string {
213235 // default to the request Host if not set
214236 if p.redirectURL.Host != "" {
@@ -218,9 +240,9 @@ func (p *OAuthProxy) GetRedirectURI(host string) string {
218240 u = *p.redirectURL
219241 if u.Scheme == "" {
220242 if p.CookieSecure {
221− u.Scheme = "https"
243+ u.Scheme = httpsScheme
222244 } else {
223− u.Scheme = "http"
245+ u.Scheme = httpScheme
224246 }
225247 }
226248 u.Host = host
@@ -254,6 +276,8 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
254276 return
255277}
256278
279+// MakeSessionCookie creates an http.Cookie containing the authenticated user's
280+// authentication details
257281func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
258282 if value != "" {
259283 value = cookie.SignedValue(p.CookieSeed, p.CookieName, value, now)
@@ -265,6 +289,7 @@ func (p *OAuthProxy) MakeSessionCookie(req *http.Request, value string, expirati
265289 return p.makeCookie(req, p.CookieName, value, expiration, now)
266290}
267291
292+// MakeCSRFCookie creates a cookie for CSRF
268293func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
269294 return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
270295}
@@ -285,20 +310,25 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
285310 Value: value,
286311 Path: "/",
287312 Domain: p.CookieDomain,
288− HttpOnly: p.CookieHttpOnly,
313+ HttpOnly: p.CookieHTTPOnly,
289314 Secure: p.CookieSecure,
290315 Expires: now.Add(expiration),
291316 }
292317}
293318
319+// ClearCSRFCookie creates a cookie to unset the CSRF cookie stored in the user's
320+// session
294321func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) {
295322 http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now()))
296323}
297324
325+// SetCSRFCookie adds a CSRF cookie to the response
298326func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, val string) {
299327 http.SetCookie(rw, p.MakeCSRFCookie(req, val, p.CookieExpire, time.Now()))
300328}
301329
330+// ClearSessionCookie creates a cookie to unset the user's authentication cookie
331+// stored in the user's session
302332func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) {
303333 clr := p.MakeSessionCookie(req, "", time.Hour*-1, time.Now())
304334 http.SetCookie(rw, clr)
@@ -311,10 +341,12 @@ func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Reques
311341 }
312342}
313343
344+// SetSessionCookie adds the user's session cookie to the response
314345func (p *OAuthProxy) SetSessionCookie(rw http.ResponseWriter, req *http.Request, val string) {
315346 http.SetCookie(rw, p.MakeSessionCookie(req, val, p.CookieExpire, time.Now()))
316347}
317348
349+// LoadCookiedSession reads the user's authentication details from the request
318350func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionState, time.Duration, error) {
319351 var age time.Duration
320352 c, err := req.Cookie(p.CookieName)
@@ -336,6 +368,7 @@ func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*providers.SessionSt
336368 return session, age, nil
337369}
338370
371+// SaveSession creates a new session cookie value and sets this on the response
339372func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *providers.SessionState) error {
340373 value, err := p.provider.CookieForSession(s, p.CookieCipher)
341374 if err != nil {
@@ -345,16 +378,19 @@ func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *p
345378 return nil
346379}
347380
381+// RobotsTxt disallows scraping pages from the OAuthProxy
348382func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
349383 rw.WriteHeader(http.StatusOK)
350384 fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
351385}
352386
387+// PingPage responds 200 OK to requests
353388func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
354389 rw.WriteHeader(http.StatusOK)
355390 fmt.Fprintf(rw, "OK")
356391}
357392
393+// ErrorPage writes an error response
358394func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
359395 log.Printf("ErrorPage %d %s %s", code, title, message)
360396 rw.WriteHeader(code)
@@ -370,16 +406,17 @@ func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, m
370406 p.templates.ExecuteTemplate(rw, "error.html", t)
371407}
372408
409+// SignInPage writes the sing in template to the response
373410func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
374411 p.ClearSessionCookie(rw, req)
375412 rw.WriteHeader(code)
376413
377− redirect_url := req.URL.RequestURI()
414+ redirecURL := req.URL.RequestURI()
378415 if req.Header.Get("X-Auth-Request-Redirect") != "" {
379− redirect_url = req.Header.Get("X-Auth-Request-Redirect")
416+ redirecURL = req.Header.Get("X-Auth-Request-Redirect")
380417 }
381− if redirect_url == p.SignInPath {
382− redirect_url = "/"
418+ if redirecURL == p.SignInPath {
419+ redirecURL = "/"
383420 }
384421
385422 t := struct {
@@ -394,7 +431,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
394431 ProviderName: p.provider.Data().ProviderName,
395432 SignInMessage: p.SignInMessage,
396433 CustomLogin: p.displayCustomLoginForm(),
397− Redirect: redirect_url,
434+ Redirect: redirecURL,
398435 Version: VERSION,
399436 ProxyPrefix: p.ProxyPrefix,
400437 Footer: template.HTML(p.Footer),
@@ -402,6 +439,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
402439 p.templates.ExecuteTemplate(rw, "sign_in.html", t)
403440}
404441
442+// ManualSignIn handles basic auth logins to the proxy
405443func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (string, bool) {
406444 if req.Method != "POST" || p.HtpasswdFile == nil {
407445 return "", false
@@ -419,6 +457,8 @@ func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (st
419457 return "", false
420458}
421459
460+// GetRedirect reads the query parameter to get the URL to redirect clients to
461+// once authenticated with the OAuthProxy
422462func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) {
423463 err = req.ParseForm()
424464 if err != nil {
@@ -433,11 +473,13 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
433473 return
434474}
435475
476+// IsWhitelistedRequest is used to check if auth should be skipped for this request
436477func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) (ok bool) {
437478 isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
438479 return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
439480}
440481
482+// IsWhitelistedPath is used to check if the request path is allowed without auth
441483func (p *OAuthProxy) IsWhitelistedPath(path string) (ok bool) {
442484 for _, u := range p.compiledRegex {
443485 ok = u.MatchString(path)
@@ -479,6 +521,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
479521 }
480522}
481523
524+// SignIn serves a page prompting users to sign in
482525func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
483526 redirect, err := p.GetRedirect(req)
484527 if err != nil {
@@ -500,11 +543,13 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
500543 }
501544}
502545
546+// SignOut sends a response to clear the authentication cookie
503547func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
504548 p.ClearSessionCookie(rw, req)
505549 http.Redirect(rw, req, "/", 302)
506550}
507551
552+// OAuthStart starts the OAuth2 authentication flow
508553func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
509554 nonce, err := cookie.Nonce()
510555 if err != nil {
@@ -521,6 +566,8 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
521566 http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), 302)
522567}
523568
569+// OAuthCallback is the OAuth2 authentication flow callback that finishes the
570+// OAuth2 authentication flow
524571func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
525572 remoteAddr := getRemoteAddr(req)
526573
@@ -582,6 +629,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
582629 }
583630}
584631
632+// AuthenticateOnly checks whether the user is currently logged in
585633func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
586634 status := p.Authenticate(rw, req)
587635 if status == http.StatusAccepted {
@@ -591,6 +639,8 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
591639 }
592640}
593641
642+// Proxy proxies the user request if the user is authenticated else it prompts
643+// them to authenticate
594644func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
595645 status := p.Authenticate(rw, req)
596646 if status == http.StatusInternalServerError {
@@ -607,6 +657,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
607657 }
608658}
609659
660+// Authenticate checks whether a user is authenticated
610661func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int {
611662 var saveSession, clearSession, revalidated bool
612663 remoteAddr := getRemoteAddr(req)
@@ -620,7 +671,8 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
620671 saveSession = true
621672 }
622673
623− if ok, err := p.provider.RefreshSessionIfNeeded(session); err != nil {
674+ var ok bool
675+ if ok, err = p.provider.RefreshSessionIfNeeded(session); err != nil {
624676 log.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
625677 clearSession = true
626678 session = nil
@@ -653,7 +705,7 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
653705 }
654706
655707 if saveSession && session != nil {
656− err := p.SaveSession(rw, req, session)
708+ err = p.SaveSession(rw, req, session)
657709 if err != nil {
658710 log.Printf("%s %s", remoteAddr, err)
659711 return http.StatusInternalServerError
@@ -706,6 +758,8 @@ func (p *OAuthProxy) Authenticate(rw http.ResponseWriter, req *http.Request) int
706758 return http.StatusAccepted
707759}
708760
761+// CheckBasicAuth checks the requests Authorization header for basic auth
762+// credentials and authenticates these against the proxies HtpasswdFile
709763func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*providers.SessionState, error) {
710764 if p.HtpasswdFile == nil {
711765 return nil, nil
oauthproxy_test.go+148−150
@@ -15,8 +15,8 @@ import (
1515 "testing"
1616 "time"
1717
18− "github.com/bitly/oauth2_proxy/providers"
1918 "github.com/mbland/hmacauth"
19+ "github.com/pusher/oauth2_proxy/providers"
2020 "github.com/stretchr/testify/assert"
2121)
2222
@@ -98,28 +98,28 @@ type TestProvider struct {
9898 ValidToken bool
9999}
100100
101−func NewTestProvider(provider_url *url.URL, email_address string) *TestProvider {
101+func NewTestProvider(providerURL *url.URL, emailAddress string) *TestProvider {
102102 return &TestProvider{
103103 ProviderData: &providers.ProviderData{
104104 ProviderName: "Test Provider",
105105 LoginURL: &url.URL{
106106 Scheme: "http",
107− Host: provider_url.Host,
107+ Host: providerURL.Host,
108108 Path: "/oauth/authorize",
109109 },
110110 RedeemURL: &url.URL{
111111 Scheme: "http",
112− Host: provider_url.Host,
112+ Host: providerURL.Host,
113113 Path: "/oauth/token",
114114 },
115115 ProfileURL: &url.URL{
116116 Scheme: "http",
117− Host: provider_url.Host,
117+ Host: providerURL.Host,
118118 Path: "/api/v1/profile",
119119 },
120120 Scope: "profile.email",
121121 },
122− EmailAddress: email_address,
122+ EmailAddress: emailAddress,
123123 }
124124}
125125
@@ -132,11 +132,10 @@ func (tp *TestProvider) ValidateSessionState(session *providers.SessionState) bo
132132}
133133
134134func TestBasicAuthPassword(t *testing.T) {
135− provider_server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
135+ providerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
136136 log.Printf("%#v", r)
137− url := r.URL
138− payload := ""
139− switch url.Path {
137+ var payload string
138+ switch r.URL.Path {
140139 case "/oauth/token":
141140 payload = `{"access_token": "my_auth_token"}`
142141 default:
@@ -149,7 +148,7 @@ func TestBasicAuthPassword(t *testing.T) {
149148 w.Write([]byte(payload))
150149 }))
151150 opts := NewOptions()
152− opts.Upstreams = append(opts.Upstreams, provider_server.URL)
151+ opts.Upstreams = append(opts.Upstreams, providerServer.URL)
153152 // The CookieSecret must be 32 bytes in order to create the AES
154153 // cipher.
155154 opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
@@ -161,13 +160,13 @@ func TestBasicAuthPassword(t *testing.T) {
161160 opts.BasicAuthPassword = "This is a secure password"
162161 opts.Validate()
163162
164− provider_url, _ := url.Parse(provider_server.URL)
165− const email_address = "michael.bland@gsa.gov"
166− const user_name = "michael.bland"
163+ providerURL, _ := url.Parse(providerServer.URL)
164+ const emailAddress = "michael.bland@gsa.gov"
165+ const username = "michael.bland"
167166
168− opts.provider = NewTestProvider(provider_url, email_address)
167+ opts.provider = NewTestProvider(providerURL, emailAddress)
169168 proxy := NewOAuthProxy(opts, func(email string) bool {
170− return email == email_address
169+ return email == emailAddress
171170 })
172171
173172 rw := httptest.NewRecorder()
@@ -182,10 +181,10 @@ func TestBasicAuthPassword(t *testing.T) {
182181
183182 cookieName := proxy.CookieName
184183 var value string
185− key_prefix := cookieName + "="
184+ keyPrefix := cookieName + "="
186185
187186 for _, field := range strings.Split(cookie, "; ") {
188− value = strings.TrimPrefix(field, key_prefix)
187+ value = strings.TrimPrefix(field, keyPrefix)
189188 if value != field {
190189 break
191190 } else {
@@ -206,15 +205,15 @@ func TestBasicAuthPassword(t *testing.T) {
206205 rw = httptest.NewRecorder()
207206 proxy.ServeHTTP(rw, req)
208207
209− expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(user_name+":"+opts.BasicAuthPassword))
208+ expectedHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+opts.BasicAuthPassword))
210209 assert.Equal(t, expectedHeader, rw.Body.String())
211− provider_server.Close()
210+ providerServer.Close()
212211}
213212
214213type PassAccessTokenTest struct {
215− provider_server *httptest.Server
216− proxy *OAuthProxy
217− opts *Options
214+ providerServer *httptest.Server
215+ proxy *OAuthProxy
216+ opts *Options
218217}
219218
220219type PassAccessTokenTestOptions struct {
@@ -224,12 +223,11 @@ type PassAccessTokenTestOptions struct {
224223func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
225224 t := &PassAccessTokenTest{}
226225
227− t.provider_server = httptest.NewServer(
226+ t.providerServer = httptest.NewServer(
228227 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
229228 log.Printf("%#v", r)
230− url := r.URL
231− payload := ""
232− switch url.Path {
229+ var payload string
230+ switch r.URL.Path {
233231 case "/oauth/token":
234232 payload = `{"access_token": "my_auth_token"}`
235233 default:
@@ -243,7 +241,7 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
243241 }))
244242
245243 t.opts = NewOptions()
246− t.opts.Upstreams = append(t.opts.Upstreams, t.provider_server.URL)
244+ t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
247245 // The CookieSecret must be 32 bytes in order to create the AES
248246 // cipher.
249247 t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
@@ -253,21 +251,21 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
253251 t.opts.PassAccessToken = opts.PassAccessToken
254252 t.opts.Validate()
255253
256− provider_url, _ := url.Parse(t.provider_server.URL)
257− const email_address = "michael.bland@gsa.gov"
254+ providerURL, _ := url.Parse(t.providerServer.URL)
255+ const emailAddress = "michael.bland@gsa.gov"
258256
259− t.opts.provider = NewTestProvider(provider_url, email_address)
257+ t.opts.provider = NewTestProvider(providerURL, emailAddress)
260258 t.proxy = NewOAuthProxy(t.opts, func(email string) bool {
261− return email == email_address
259+ return email == emailAddress
262260 })
263261 return t
264262}
265263
266−func (pat_test *PassAccessTokenTest) Close() {
267− pat_test.provider_server.Close()
264+func (patTest *PassAccessTokenTest) Close() {
265+ patTest.providerServer.Close()
268266}
269267
270−func (pat_test *PassAccessTokenTest) getCallbackEndpoint() (http_code int,
268+func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
271269 cookie string) {
272270 rw := httptest.NewRecorder()
273271 req, err := http.NewRequest("GET", "/oauth2/callback?code=callback_code&state=nonce:",
@@ -275,18 +273,18 @@ func (pat_test *PassAccessTokenTest) getCallbackEndpoint() (http_code int,
275273 if err != nil {
276274 return 0, ""
277275 }
278− req.AddCookie(pat_test.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
279− pat_test.proxy.ServeHTTP(rw, req)
276+ req.AddCookie(patTest.proxy.MakeCSRFCookie(req, "nonce", time.Hour, time.Now()))
277+ patTest.proxy.ServeHTTP(rw, req)
280278 return rw.Code, rw.HeaderMap["Set-Cookie"][1]
281279}
282280
283−func (pat_test *PassAccessTokenTest) getRootEndpoint(cookie string) (http_code int, access_token string) {
284− cookieName := pat_test.proxy.CookieName
281+func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int, accessToken string) {
282+ cookieName := patTest.proxy.CookieName
285283 var value string
286− key_prefix := cookieName + "="
284+ keyPrefix := cookieName + "="
287285
288286 for _, field := range strings.Split(cookie, "; ") {
289− value = strings.TrimPrefix(field, key_prefix)
287+ value = strings.TrimPrefix(field, keyPrefix)
290288 if value != field {
291289 break
292290 } else {
@@ -310,18 +308,18 @@ func (pat_test *PassAccessTokenTest) getRootEndpoint(cookie string) (http_code i
310308 })
311309
312310 rw := httptest.NewRecorder()
313− pat_test.proxy.ServeHTTP(rw, req)
311+ patTest.proxy.ServeHTTP(rw, req)
314312 return rw.Code, rw.Body.String()
315313}
316314
317315func TestForwardAccessTokenUpstream(t *testing.T) {
318− pat_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
316+ patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
319317 PassAccessToken: true,
320318 })
321− defer pat_test.Close()
319+ defer patTest.Close()
322320
323321 // A successful validation will redirect and set the auth cookie.
324− code, cookie := pat_test.getCallbackEndpoint()
322+ code, cookie := patTest.getCallbackEndpoint()
325323 if code != 302 {
326324 t.Fatalf("expected 302; got %d", code)
327325 }
@@ -330,7 +328,7 @@ func TestForwardAccessTokenUpstream(t *testing.T) {
330328 // Now we make a regular request; the access_token from the cookie is
331329 // forwarded as the "X-Forwarded-Access-Token" header. The token is
332330 // read by the test provider server and written in the response body.
333− code, payload := pat_test.getRootEndpoint(cookie)
331+ code, payload := patTest.getRootEndpoint(cookie)
334332 if code != 200 {
335333 t.Fatalf("expected 200; got %d", code)
336334 }
@@ -338,13 +336,13 @@ func TestForwardAccessTokenUpstream(t *testing.T) {
338336}
339337
340338func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
341− pat_test := NewPassAccessTokenTest(PassAccessTokenTestOptions{
339+ patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
342340 PassAccessToken: false,
343341 })
344− defer pat_test.Close()
342+ defer patTest.Close()
345343
346344 // A successful validation will redirect and set the auth cookie.
347− code, cookie := pat_test.getCallbackEndpoint()
345+ code, cookie := patTest.getCallbackEndpoint()
348346 if code != 302 {
349347 t.Fatalf("expected 302; got %d", code)
350348 }
@@ -352,7 +350,7 @@ func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
352350
353351 // Now we make a regular request, but the access token header should
354352 // not be present.
355− code, payload := pat_test.getRootEndpoint(cookie)
353+ code, payload := patTest.getRootEndpoint(cookie)
356354 if code != 200 {
357355 t.Fatalf("expected 200; got %d", code)
358356 }
@@ -360,49 +358,49 @@ func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
360358}
361359
362360type SignInPageTest struct {
363− opts *Options
364− proxy *OAuthProxy
365− sign_in_regexp *regexp.Regexp
366− sign_in_provider_regexp *regexp.Regexp
361+ opts *Options
362+ proxy *OAuthProxy
363+ signInRegexp *regexp.Regexp
364+ signInProviderRegexp *regexp.Regexp
367365}
368366
369367const signInRedirectPattern = `<input type="hidden" name="rd" value="(.*)">`
370368const signInSkipProvider = `>Found<`
371369
372370func NewSignInPageTest(skipProvider bool) *SignInPageTest {
373− var sip_test SignInPageTest
371+ var sipTest SignInPageTest
374372
375− sip_test.opts = NewOptions()
376− sip_test.opts.CookieSecret = "foobar"
377− sip_test.opts.ClientID = "bazquux"
378− sip_test.opts.ClientSecret = "xyzzyplugh"
379− sip_test.opts.SkipProviderButton = skipProvider
380− sip_test.opts.Validate()
373+ sipTest.opts = NewOptions()
374+ sipTest.opts.CookieSecret = "foobar"
375+ sipTest.opts.ClientID = "bazquux"
376+ sipTest.opts.ClientSecret = "xyzzyplugh"
377+ sipTest.opts.SkipProviderButton = skipProvider
378+ sipTest.opts.Validate()
381379
382− sip_test.proxy = NewOAuthProxy(sip_test.opts, func(email string) bool {
380+ sipTest.proxy = NewOAuthProxy(sipTest.opts, func(email string) bool {
383381 return true
384382 })
385− sip_test.sign_in_regexp = regexp.MustCompile(signInRedirectPattern)
386− sip_test.sign_in_provider_regexp = regexp.MustCompile(signInSkipProvider)
383+ sipTest.signInRegexp = regexp.MustCompile(signInRedirectPattern)
384+ sipTest.signInProviderRegexp = regexp.MustCompile(signInSkipProvider)
387385
388− return &sip_test
386+ return &sipTest
389387}
390388
391−func (sip_test *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
389+func (sipTest *SignInPageTest) GetEndpoint(endpoint string) (int, string) {
392390 rw := httptest.NewRecorder()
393391 req, _ := http.NewRequest("GET", endpoint, strings.NewReader(""))
394− sip_test.proxy.ServeHTTP(rw, req)
392+ sipTest.proxy.ServeHTTP(rw, req)
395393 return rw.Code, rw.Body.String()
396394}
397395
398396func TestSignInPageIncludesTargetRedirect(t *testing.T) {
399− sip_test := NewSignInPageTest(false)
397+ sipTest := NewSignInPageTest(false)
400398 const endpoint = "/some/random/endpoint"
401399
402− code, body := sip_test.GetEndpoint(endpoint)
400+ code, body := sipTest.GetEndpoint(endpoint)
403401 assert.Equal(t, 403, code)
404402
405− match := sip_test.sign_in_regexp.FindStringSubmatch(body)
403+ match := sipTest.signInRegexp.FindStringSubmatch(body)
406404 if match == nil {
407405 t.Fatal("Did not find pattern in body: " +
408406 signInRedirectPattern + "\nBody:\n" + body)
@@ -414,11 +412,11 @@ func TestSignInPageIncludesTargetRedirect(t *testing.T) {
414412}
415413
416414func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
417− sip_test := NewSignInPageTest(false)
418− code, body := sip_test.GetEndpoint("/oauth2/sign_in")
415+ sipTest := NewSignInPageTest(false)
416+ code, body := sipTest.GetEndpoint("/oauth2/sign_in")
419417 assert.Equal(t, 200, code)
420418
421− match := sip_test.sign_in_regexp.FindStringSubmatch(body)
419+ match := sipTest.signInRegexp.FindStringSubmatch(body)
422420 if match == nil {
423421 t.Fatal("Did not find pattern in body: " +
424422 signInRedirectPattern + "\nBody:\n" + body)
@@ -429,13 +427,13 @@ func TestSignInPageDirectAccessRedirectsToRoot(t *testing.T) {
429427}
430428
431429func TestSignInPageSkipProvider(t *testing.T) {
432− sip_test := NewSignInPageTest(true)
430+ sipTest := NewSignInPageTest(true)
433431 const endpoint = "/some/random/endpoint"
434432
435− code, body := sip_test.GetEndpoint(endpoint)
433+ code, body := sipTest.GetEndpoint(endpoint)
436434 assert.Equal(t, 302, code)
437435
438− match := sip_test.sign_in_provider_regexp.FindStringSubmatch(body)
436+ match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
439437 if match == nil {
440438 t.Fatal("Did not find pattern in body: " +
441439 signInSkipProvider + "\nBody:\n" + body)
@@ -443,13 +441,13 @@ func TestSignInPageSkipProvider(t *testing.T) {
443441}
444442
445443func TestSignInPageSkipProviderDirect(t *testing.T) {
446− sip_test := NewSignInPageTest(true)
444+ sipTest := NewSignInPageTest(true)
447445 const endpoint = "/sign_in"
448446
449− code, body := sip_test.GetEndpoint(endpoint)
447+ code, body := sipTest.GetEndpoint(endpoint)
450448 assert.Equal(t, 302, code)
451449
452− match := sip_test.sign_in_provider_regexp.FindStringSubmatch(body)
450+ match := sipTest.signInProviderRegexp.FindStringSubmatch(body)
453451 if match == nil {
454452 t.Fatal("Did not find pattern in body: " +
455453 signInSkipProvider + "\nBody:\n" + body)
@@ -457,50 +455,50 @@ func TestSignInPageSkipProviderDirect(t *testing.T) {
457455}
458456
459457type ProcessCookieTest struct {
460− opts *Options
461− proxy *OAuthProxy
462− rw *httptest.ResponseRecorder
463− req *http.Request
464− provider TestProvider
465− response_code int
466− validate_user bool
458+ opts *Options
459+ proxy *OAuthProxy
460+ rw *httptest.ResponseRecorder
461+ req *http.Request
462+ provider TestProvider
463+ responseCode int
464+ validateUser bool
467465}
468466
469467type ProcessCookieTestOpts struct {
470− provider_validate_cookie_response bool
468+ providerValidateCookieResponse bool
471469}
472470
473471func NewProcessCookieTest(opts ProcessCookieTestOpts) *ProcessCookieTest {
474− var pc_test ProcessCookieTest
472+ var pcTest ProcessCookieTest
475473
476− pc_test.opts = NewOptions()
477− pc_test.opts.ClientID = "bazquux"
478− pc_test.opts.ClientSecret = "xyzzyplugh"
479− pc_test.opts.CookieSecret = "0123456789abcdefabcd"
474+ pcTest.opts = NewOptions()
475+ pcTest.opts.ClientID = "bazquux"
476+ pcTest.opts.ClientSecret = "xyzzyplugh"
477+ pcTest.opts.CookieSecret = "0123456789abcdefabcd"
480478 // First, set the CookieRefresh option so proxy.AesCipher is created,
481479 // needed to encrypt the access_token.
482− pc_test.opts.CookieRefresh = time.Hour
483− pc_test.opts.Validate()
480+ pcTest.opts.CookieRefresh = time.Hour
481+ pcTest.opts.Validate()
484482
485− pc_test.proxy = NewOAuthProxy(pc_test.opts, func(email string) bool {
486− return pc_test.validate_user
483+ pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
484+ return pcTest.validateUser
487485 })
488− pc_test.proxy.provider = &TestProvider{
489− ValidToken: opts.provider_validate_cookie_response,
486+ pcTest.proxy.provider = &TestProvider{
487+ ValidToken: opts.providerValidateCookieResponse,
490488 }
491489
492490 // Now, zero-out proxy.CookieRefresh for the cases that don't involve
493491 // access_token validation.
494− pc_test.proxy.CookieRefresh = time.Duration(0)
495− pc_test.rw = httptest.NewRecorder()
496− pc_test.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
497− pc_test.validate_user = true
498− return &pc_test
492+ pcTest.proxy.CookieRefresh = time.Duration(0)
493+ pcTest.rw = httptest.NewRecorder()
494+ pcTest.req, _ = http.NewRequest("GET", "/", strings.NewReader(""))
495+ pcTest.validateUser = true
496+ return &pcTest
499497}
500498
501499func NewProcessCookieTestWithDefaults() *ProcessCookieTest {
502500 return NewProcessCookieTest(ProcessCookieTestOpts{
503− provider_validate_cookie_response: true,
501+ providerValidateCookieResponse: true,
504502 })
505503}
506504
@@ -522,12 +520,12 @@ func (p *ProcessCookieTest) LoadCookiedSession() (*providers.SessionState, time.
522520}
523521
524522func TestLoadCookiedSession(t *testing.T) {
525− pc_test := NewProcessCookieTestWithDefaults()
523+ pcTest := NewProcessCookieTestWithDefaults()
526524
527525 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
528− pc_test.SaveSession(startSession, time.Now())
526+ pcTest.SaveSession(startSession, time.Now())
529527
530− session, _, err := pc_test.LoadCookiedSession()
528+ session, _, err := pcTest.LoadCookiedSession()
531529 assert.Equal(t, nil, err)
532530 assert.Equal(t, startSession.Email, session.Email)
533531 assert.Equal(t, "michael.bland", session.User)
@@ -535,9 +533,9 @@ func TestLoadCookiedSession(t *testing.T) {
535533}
536534
537535func TestProcessCookieNoCookieError(t *testing.T) {
538− pc_test := NewProcessCookieTestWithDefaults()
536+ pcTest := NewProcessCookieTestWithDefaults()
539537
540− session, _, err := pc_test.LoadCookiedSession()
538+ session, _, err := pcTest.LoadCookiedSession()
541539 assert.Equal(t, "Cookie \"_oauth2_proxy\" not present", err.Error())
542540 if session != nil {
543541 t.Errorf("expected nil session. got %#v", session)
@@ -545,14 +543,14 @@ func TestProcessCookieNoCookieError(t *testing.T) {
545543}
546544
547545func TestProcessCookieRefreshNotSet(t *testing.T) {
548− pc_test := NewProcessCookieTestWithDefaults()
549− pc_test.proxy.CookieExpire = time.Duration(23) * time.Hour
546+ pcTest := NewProcessCookieTestWithDefaults()
547+ pcTest.proxy.CookieExpire = time.Duration(23) * time.Hour
550548 reference := time.Now().Add(time.Duration(-2) * time.Hour)
551549
552550 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
553− pc_test.SaveSession(startSession, reference)
551+ pcTest.SaveSession(startSession, reference)
554552
555− session, age, err := pc_test.LoadCookiedSession()
553+ session, age, err := pcTest.LoadCookiedSession()
556554 assert.Equal(t, nil, err)
557555 if age < time.Duration(-2)*time.Hour {
558556 t.Errorf("cookie too young %v", age)
@@ -561,13 +559,13 @@ func TestProcessCookieRefreshNotSet(t *testing.T) {
561559}
562560
563561func TestProcessCookieFailIfCookieExpired(t *testing.T) {
564− pc_test := NewProcessCookieTestWithDefaults()
565− pc_test.proxy.CookieExpire = time.Duration(24) * time.Hour
562+ pcTest := NewProcessCookieTestWithDefaults()
563+ pcTest.proxy.CookieExpire = time.Duration(24) * time.Hour
566564 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
567565 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
568− pc_test.SaveSession(startSession, reference)
566+ pcTest.SaveSession(startSession, reference)
569567
570− session, _, err := pc_test.LoadCookiedSession()
568+ session, _, err := pcTest.LoadCookiedSession()
571569 assert.NotEqual(t, nil, err)
572570 if session != nil {
573571 t.Errorf("expected nil session %#v", session)
@@ -575,14 +573,14 @@ func TestProcessCookieFailIfCookieExpired(t *testing.T) {
575573}
576574
577575func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
578− pc_test := NewProcessCookieTestWithDefaults()
579− pc_test.proxy.CookieExpire = time.Duration(24) * time.Hour
576+ pcTest := NewProcessCookieTestWithDefaults()
577+ pcTest.proxy.CookieExpire = time.Duration(24) * time.Hour
580578 reference := time.Now().Add(time.Duration(25) * time.Hour * -1)
581579 startSession := &providers.SessionState{Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
582− pc_test.SaveSession(startSession, reference)
580+ pcTest.SaveSession(startSession, reference)
583581
584− pc_test.proxy.CookieRefresh = time.Hour
585− session, _, err := pc_test.LoadCookiedSession()
582+ pcTest.proxy.CookieRefresh = time.Hour
583+ session, _, err := pcTest.LoadCookiedSession()
586584 assert.NotEqual(t, nil, err)
587585 if session != nil {
588586 t.Errorf("expected nil session %#v", session)
@@ -590,10 +588,10 @@ func TestProcessCookieFailIfRefreshSetAndCookieExpired(t *testing.T) {
590588}
591589
592590func NewAuthOnlyEndpointTest() *ProcessCookieTest {
593− pc_test := NewProcessCookieTestWithDefaults()
594− pc_test.req, _ = http.NewRequest("GET",
595− pc_test.opts.ProxyPrefix+"/auth", nil)
596− return pc_test
591+ pcTest := NewProcessCookieTestWithDefaults()
592+ pcTest.req, _ = http.NewRequest("GET",
593+ pcTest.opts.ProxyPrefix+"/auth", nil)
594+ return pcTest
597595}
598596
599597func TestAuthOnlyEndpointAccepted(t *testing.T) {
@@ -636,7 +634,7 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
636634 startSession := &providers.SessionState{
637635 Email: "michael.bland@gsa.gov", AccessToken: "my_access_token"}
638636 test.SaveSession(startSession, time.Now())
639− test.validate_user = false
637+ test.validateUser = false
640638
641639 test.proxy.ServeHTTP(test.rw, test.req)
642640 assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
@@ -645,33 +643,33 @@ func TestAuthOnlyEndpointUnauthorizedOnEmailValidationFailure(t *testing.T) {
645643}
646644
647645func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
648− var pc_test ProcessCookieTest
646+ var pcTest ProcessCookieTest
649647
650− pc_test.opts = NewOptions()
651− pc_test.opts.SetXAuthRequest = true
652− pc_test.opts.Validate()
648+ pcTest.opts = NewOptions()
649+ pcTest.opts.SetXAuthRequest = true
650+ pcTest.opts.Validate()
653651
654− pc_test.proxy = NewOAuthProxy(pc_test.opts, func(email string) bool {
655− return pc_test.validate_user
652+ pcTest.proxy = NewOAuthProxy(pcTest.opts, func(email string) bool {
653+ return pcTest.validateUser
656654 })
657− pc_test.proxy.provider = &TestProvider{
655+ pcTest.proxy.provider = &TestProvider{
658656 ValidToken: true,
659657 }
660658
661− pc_test.validate_user = true
659+ pcTest.validateUser = true
662660
663− pc_test.rw = httptest.NewRecorder()
664− pc_test.req, _ = http.NewRequest("GET",
665− pc_test.opts.ProxyPrefix+"/auth", nil)
661+ pcTest.rw = httptest.NewRecorder()
662+ pcTest.req, _ = http.NewRequest("GET",
663+ pcTest.opts.ProxyPrefix+"/auth", nil)
666664
667665 startSession := &providers.SessionState{
668666 User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token"}
669− pc_test.SaveSession(startSession, time.Now())
667+ pcTest.SaveSession(startSession, time.Now())
670668
671− pc_test.proxy.ServeHTTP(pc_test.rw, pc_test.req)
672− assert.Equal(t, http.StatusAccepted, pc_test.rw.Code)
673− assert.Equal(t, "oauth_user", pc_test.rw.HeaderMap["X-Auth-Request-User"][0])
674− assert.Equal(t, "oauth_user@example.com", pc_test.rw.HeaderMap["X-Auth-Request-Email"][0])
669+ pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
670+ assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
671+ assert.Equal(t, "oauth_user", pcTest.rw.HeaderMap["X-Auth-Request-User"][0])
672+ assert.Equal(t, "oauth_user@example.com", pcTest.rw.HeaderMap["X-Auth-Request-Email"][0])
675673}
676674
677675func TestAuthSkippedForPreflightRequests(t *testing.T) {
@@ -689,8 +687,8 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
689687 opts.SkipAuthPreflight = true
690688 opts.Validate()
691689
692− upstream_url, _ := url.Parse(upstream.URL)
693− opts.provider = NewTestProvider(upstream_url, "")
690+ upstreamURL, _ := url.Parse(upstream.URL)
691+ opts.provider = NewTestProvider(upstreamURL, "")
694692
695693 proxy := NewOAuthProxy(opts, func(string) bool { return false })
696694 rw := httptest.NewRecorder()
@@ -723,7 +721,7 @@ func (v *SignatureAuthenticator) Authenticate(w http.ResponseWriter, r *http.Req
723721type SignatureTest struct {
724722 opts *Options
725723 upstream *httptest.Server
726− upstream_host string
724+ upstreamHost string
727725 provider *httptest.Server
728726 header http.Header
729727 rw *httptest.ResponseRecorder
@@ -740,20 +738,20 @@ func NewSignatureTest() *SignatureTest {
740738 authenticator := &SignatureAuthenticator{}
741739 upstream := httptest.NewServer(
742740 http.HandlerFunc(authenticator.Authenticate))
743− upstream_url, _ := url.Parse(upstream.URL)
741+ upstreamURL, _ := url.Parse(upstream.URL)
744742 opts.Upstreams = append(opts.Upstreams, upstream.URL)
745743
746744 providerHandler := func(w http.ResponseWriter, r *http.Request) {
747745 w.Write([]byte(`{"access_token": "my_auth_token"}`))
748746 }
749747 provider := httptest.NewServer(http.HandlerFunc(providerHandler))
750− provider_url, _ := url.Parse(provider.URL)
751− opts.provider = NewTestProvider(provider_url, "mbland@acm.org")
748+ providerURL, _ := url.Parse(provider.URL)
749+ opts.provider = NewTestProvider(providerURL, "mbland@acm.org")
752750
753751 return &SignatureTest{
754752 opts,
755753 upstream,
756− upstream_url.Host,
754+ upstreamURL.Host,
757755 provider,
758756 make(http.Header),
759757 httptest.NewRecorder(),
options.go+23−17
@@ -13,16 +13,17 @@ import (
1313 "strings"
1414 "time"
1515
16− "github.com/bitly/oauth2_proxy/providers"
1716 oidc "github.com/coreos/go-oidc"
1817 "github.com/mbland/hmacauth"
18+ "github.com/pusher/oauth2_proxy/providers"
1919)
2020
21−// Configuration Options that can be set by Command Line Flag, or Config File
21+// Options holds Configuration Options that can be set by Command Line Flag,
22+// or Config File
2223type Options struct {
2324 ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy-prefix"`
24− HttpAddress string `flag:"http-address" cfg:"http_address"`
25− HttpsAddress string `flag:"https-address" cfg:"https_address"`
25+ HTTPAddress string `flag:"http-address" cfg:"http_address"`
26+ HTTPSAddress string `flag:"https-address" cfg:"https_address"`
2627 RedirectURL string `flag:"redirect-url" cfg:"redirect_url"`
2728 ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
2829 ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
@@ -48,7 +49,7 @@ type Options struct {
4849 CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"OAUTH2_PROXY_COOKIE_EXPIRE"`
4950 CookieRefresh time.Duration `flag:"cookie-refresh" cfg:"cookie_refresh" env:"OAUTH2_PROXY_COOKIE_REFRESH"`
5051 CookieSecure bool `flag:"cookie-secure" cfg:"cookie_secure"`
51− CookieHttpOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"`
52+ CookieHTTPOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"`
5253
5354 Upstreams []string `flag:"upstream" cfg:"upstreams"`
5455 SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
@@ -88,20 +89,22 @@ type Options struct {
8889 oidcVerifier *oidc.IDTokenVerifier
8990}
9091
92+// SignatureData holds hmacauth signature hash and key
9193type SignatureData struct {
9294 hash crypto.Hash
9395 key string
9496}
9597
98+// NewOptions constructs a new Options with defaulted values
9699func NewOptions() *Options {
97100 return &Options{
98101 ProxyPrefix: "/oauth2",
99− HttpAddress: "127.0.0.1:4180",
100− HttpsAddress: ":443",
102+ HTTPAddress: "127.0.0.1:4180",
103+ HTTPSAddress: ":443",
101104 DisplayHtpasswdForm: true,
102105 CookieName: "_oauth2_proxy",
103106 CookieSecure: true,
104− CookieHttpOnly: true,
107+ CookieHTTPOnly: true,
105108 CookieExpire: time.Duration(168) * time.Hour,
106109 CookieRefresh: time.Duration(0),
107110 SetXAuthRequest: false,
@@ -116,15 +119,17 @@ func NewOptions() *Options {
116119 }
117120}
118121
119−func parseURL(to_parse string, urltype string, msgs []string) (*url.URL, []string) {
120− parsed, err := url.Parse(to_parse)
122+func parseURL(toParse string, urltype string, msgs []string) (*url.URL, []string) {
123+ parsed, err := url.Parse(toParse)
121124 if err != nil {
122125 return nil, append(msgs, fmt.Sprintf(
123− "error parsing %s-url=%q %s", urltype, to_parse, err))
126+ "error parsing %s-url=%q %s", urltype, toParse, err))
124127 }
125128 return parsed, msgs
126129}
127130
131+// Validate checks that required options are set and validates those that they
132+// are of the correct format
128133func (o *Options) Validate() error {
129134 if o.SSLInsecureSkipVerify {
130135 // TODO: Accept a certificate bundle.
@@ -190,17 +195,17 @@ func (o *Options) Validate() error {
190195 msgs = parseProviderInfo(o, msgs)
191196
192197 if o.PassAccessToken || (o.CookieRefresh != time.Duration(0)) {
193− valid_cookie_secret_size := false
198+ validCookieSecretSize := false
194199 for _, i := range []int{16, 24, 32} {
195200 if len(secretBytes(o.CookieSecret)) == i {
196− valid_cookie_secret_size = true
201+ validCookieSecretSize = true
197202 }
198203 }
199204 var decoded bool
200205 if string(secretBytes(o.CookieSecret)) != o.CookieSecret {
201206 decoded = true
202207 }
203− if valid_cookie_secret_size == false {
208+ if validCookieSecretSize == false {
204209 var suffix string
205210 if decoded {
206211 suffix = fmt.Sprintf(" note: cookie secret was base64 decoded from %q", o.CookieSecret)
@@ -294,12 +299,13 @@ func parseSignatureKey(o *Options, msgs []string) []string {
294299 }
295300
296301 algorithm, secretKey := components[0], components[1]
297− if hash, err := hmacauth.DigestNameToCryptoHash(algorithm); err != nil {
302+ var hash crypto.Hash
303+ var err error
304+ if hash, err = hmacauth.DigestNameToCryptoHash(algorithm); err != nil {
298305 return append(msgs, "unsupported signature hash algorithm: "+
299306 o.SignatureKey)
300− } else {
301− o.signatureData = &SignatureData{hash, secretKey}
302307 }
308+ o.signatureData = &SignatureData{hash, secretKey}
303309 return msgs
304310}
305311
options_test.go+2−2
@@ -88,9 +88,9 @@ func TestProxyURLs(t *testing.T) {
8888 o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081")
8989 assert.Equal(t, nil, o.Validate())
9090 expected := []*url.URL{
91− &url.URL{Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
91+ {Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
9292 // note the '/' was added
93− &url.URL{Scheme: "http", Host: "127.0.0.1:8081", Path: "/"},
93+ {Scheme: "http", Host: "127.0.0.1:8081", Path: "/"},
9494 }
9595 assert.Equal(t, expected, o.proxyURLs)
9696}
providers/azure.go+9−4
@@ -3,18 +3,21 @@ package providers
33import (
44 "errors"
55 "fmt"
6− "github.com/bitly/go-simplejson"
7− "github.com/bitly/oauth2_proxy/api"
86 "log"
97 "net/http"
108 "net/url"
9+
10+ "github.com/bitly/go-simplejson"
11+ "github.com/pusher/oauth2_proxy/api"
1112)
1213
14+// AzureProvider represents an Azure based Identity Provider
1315type AzureProvider struct {
1416 *ProviderData
1517 Tenant string
1618}
1719
20+// NewAzureProvider initiates a new AzureProvider
1821func NewAzureProvider(p *ProviderData) *AzureProvider {
1922 p.ProviderName = "Azure"
2023
@@ -39,6 +42,7 @@ func NewAzureProvider(p *ProviderData) *AzureProvider {
3942 return &AzureProvider{ProviderData: p}
4043}
4144
45+// Configure defaults the AzureProvider configuration options
4246func (p *AzureProvider) Configure(tenant string) {
4347 p.Tenant = tenant
4448 if tenant == "" {
@@ -60,9 +64,9 @@ func (p *AzureProvider) Configure(tenant string) {
6064 }
6165}
6266
63−func getAzureHeader(access_token string) http.Header {
67+func getAzureHeader(accessToken string) http.Header {
6468 header := make(http.Header)
65− header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token))
69+ header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
6670 return header
6771}
6872
@@ -83,6 +87,7 @@ func getEmailFromJSON(json *simplejson.Json) (string, error) {
8387 return email, err
8488}
8589
90+// GetEmailAddress returns the Account email address
8691func (p *AzureProvider) GetEmailAddress(s *SessionState) (string, error) {
8792 var email string
8893 var err error
providers/azure_test.go+1−2
@@ -110,8 +110,7 @@ func testAzureBackend(payload string) *httptest.Server {
110110
111111 return httptest.NewServer(http.HandlerFunc(
112112 func(w http.ResponseWriter, r *http.Request) {
113− url := r.URL
114− if url.Path != path || url.RawQuery != query {
113+ if r.URL.Path != path || r.URL.RawQuery != query {
115114 w.WriteHeader(404)
116115 } else if r.Header.Get("Authorization") != "Bearer imaginary_access_token" {
117116 w.WriteHeader(403)
providers/facebook.go+8−4
@@ -6,13 +6,15 @@ import (
66 "net/http"
77 "net/url"
88
9− "github.com/bitly/oauth2_proxy/api"
9+ "github.com/pusher/oauth2_proxy/api"
1010)
1111
12+// FacebookProvider represents an Facebook based Identity Provider
1213type FacebookProvider struct {
1314 *ProviderData
1415}
1516
17+// NewFacebookProvider initiates a new FacebookProvider
1618func NewFacebookProvider(p *ProviderData) *FacebookProvider {
1719 p.ProviderName = "Facebook"
1820 if p.LoginURL.String() == "" {
@@ -43,14 +45,15 @@ func NewFacebookProvider(p *ProviderData) *FacebookProvider {
4345 return &FacebookProvider{ProviderData: p}
4446}
4547
46−func getFacebookHeader(access_token string) http.Header {
48+func getFacebookHeader(accessToken string) http.Header {
4749 header := make(http.Header)
4850 header.Set("Accept", "application/json")
4951 header.Set("x-li-format", "json")
50− header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token))
52+ header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
5153 return header
5254}
5355
56+// GetEmailAddress returns the Account email address
5457func (p *FacebookProvider) GetEmailAddress(s *SessionState) (string, error) {
5558 if s.AccessToken == "" {
5659 return "", errors.New("missing access token")
@@ -65,7 +68,7 @@ func (p *FacebookProvider) GetEmailAddress(s *SessionState) (string, error) {
6568 Email string
6669 }
6770 var r result
68− err = api.RequestJson(req, &r)
71+ err = api.RequestJSON(req, &r)
6972 if err != nil {
7073 return "", err
7174 }
@@ -75,6 +78,7 @@ func (p *FacebookProvider) GetEmailAddress(s *SessionState) (string, error) {
7578 return r.Email, nil
7679}
7780
81+// ValidateSessionState validates the AccessToken
7882func (p *FacebookProvider) ValidateSessionState(s *SessionState) bool {
7983 return validateToken(p, s.AccessToken, getFacebookHeader(s.AccessToken))
8084}
providers/github.go+8−2
@@ -12,12 +12,14 @@ import (
1212 "strings"
1313)
1414
15+// GitHubProvider represents an GitHub based Identity Provider
1516type GitHubProvider struct {
1617 *ProviderData
1718 Org string
1819 Team string
1920}
2021
22+// NewGitHubProvider initiates a new GitHubProvider
2123func NewGitHubProvider(p *ProviderData) *GitHubProvider {
2224 p.ProviderName = "GitHub"
2325 if p.LoginURL == nil || p.LoginURL.String() == "" {
@@ -47,6 +49,8 @@ func NewGitHubProvider(p *ProviderData) *GitHubProvider {
4749 }
4850 return &GitHubProvider{ProviderData: p}
4951}
52+
53+// SetOrgTeam adds GitHub org reading parameters to the OAuth2 scope
5054func (p *GitHubProvider) SetOrgTeam(org, team string) {
5155 p.Org = org
5256 p.Team = team
@@ -106,7 +110,7 @@ func (p *GitHubProvider) hasOrg(accessToken string) (bool, error) {
106110 }
107111
108112 orgs = append(orgs, op...)
109− pn += 1
113+ pn++
110114 }
111115
112116 var presentOrgs []string
@@ -186,7 +190,7 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
186190 log.Printf("Missing Team:%q from Org:%q in teams: %v", p.Team, p.Org, presentTeams)
187191 } else {
188192 var allOrgs []string
189− for org, _ := range presentOrgs {
193+ for org := range presentOrgs {
190194 allOrgs = append(allOrgs, org)
191195 }
192196 log.Printf("Missing Organization:%q in %#v", p.Org, allOrgs)
@@ -194,6 +198,7 @@ func (p *GitHubProvider) hasOrgAndTeam(accessToken string) (bool, error) {
194198 return false, nil
195199}
196200
201+// GetEmailAddress returns the Account email address
197202func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) {
198203
199204 var emails []struct {
@@ -251,6 +256,7 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) {
251256 return "", nil
252257}
253258
259+// GetUserName returns the Account user name
254260func (p *GitHubProvider) GetUserName(s *SessionState) (string, error) {
255261 var user struct {
256262 Login string `json:"login"`
providers/github_test.go+5−6
@@ -29,19 +29,18 @@ func testGitHubProvider(hostname string) *GitHubProvider {
2929
3030func testGitHubBackend(payload []string) *httptest.Server {
3131 pathToQueryMap := map[string][]string{
32− "/user": []string{""},
33− "/user/emails": []string{""},
34− "/user/orgs": []string{"limit=200&page=1", "limit=200&page=2", "limit=200&page=3"},
32+ "/user": {""},
33+ "/user/emails": {""},
34+ "/user/orgs": {"limit=200&page=1", "limit=200&page=2", "limit=200&page=3"},
3535 }
3636
3737 return httptest.NewServer(http.HandlerFunc(
3838 func(w http.ResponseWriter, r *http.Request) {
39− url := r.URL
40− query, ok := pathToQueryMap[url.Path]
39+ query, ok := pathToQueryMap[r.URL.Path]
4140 validQuery := false
4241 index := 0
4342 for i, q := range query {
44− if q == url.RawQuery {
43+ if q == r.URL.RawQuery {
4544 validQuery = true
4645 index = i
4746 }
providers/gitlab.go+4−1
@@ -5,13 +5,15 @@ import (
55 "net/http"
66 "net/url"
77
8− "github.com/bitly/oauth2_proxy/api"
8+ "github.com/pusher/oauth2_proxy/api"
99)
1010
11+// GitLabProvider represents an GitLab based Identity Provider
1112type GitLabProvider struct {
1213 *ProviderData
1314}
1415
16+// NewGitLabProvider initiates a new GitLabProvider
1517func NewGitLabProvider(p *ProviderData) *GitLabProvider {
1618 p.ProviderName = "GitLab"
1719 if p.LoginURL == nil || p.LoginURL.String() == "" {
@@ -41,6 +43,7 @@ func NewGitLabProvider(p *ProviderData) *GitLabProvider {
4143 return &GitLabProvider{ProviderData: p}
4244}
4345
46+// GetEmailAddress returns the Account email address
4447func (p *GitLabProvider) GetEmailAddress(s *SessionState) (string, error) {
4548
4649 req, err := http.NewRequest("GET",
providers/gitlab_test.go+7−8
@@ -33,8 +33,7 @@ func testGitLabBackend(payload string) *httptest.Server {
3333
3434 return httptest.NewServer(http.HandlerFunc(
3535 func(w http.ResponseWriter, r *http.Request) {
36− url := r.URL
37− if url.Path != path || url.RawQuery != query {
36+ if r.URL.Path != path || r.URL.RawQuery != query {
3837 w.WriteHeader(404)
3938 } else {
4039 w.WriteHeader(200)
@@ -87,8 +86,8 @@ func TestGitLabProviderGetEmailAddress(t *testing.T) {
8786 b := testGitLabBackend("{\"email\": \"michael.bland@gsa.gov\"}")
8887 defer b.Close()
8988
90− b_url, _ := url.Parse(b.URL)
91− p := testGitLabProvider(b_url.Host)
89+ bURL, _ := url.Parse(b.URL)
90+ p := testGitLabProvider(bURL.Host)
9291
9392 session := &SessionState{AccessToken: "imaginary_access_token"}
9493 email, err := p.GetEmailAddress(session)
@@ -102,8 +101,8 @@ func TestGitLabProviderGetEmailAddressFailedRequest(t *testing.T) {
102101 b := testGitLabBackend("unused payload")
103102 defer b.Close()
104103
105− b_url, _ := url.Parse(b.URL)
106− p := testGitLabProvider(b_url.Host)
104+ bURL, _ := url.Parse(b.URL)
105+ p := testGitLabProvider(bURL.Host)
107106
108107 // We'll trigger a request failure by using an unexpected access
109108 // token. Alternatively, we could allow the parsing of the payload as
@@ -118,8 +117,8 @@ func TestGitLabProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
118117 b := testGitLabBackend("{\"foo\": \"bar\"}")
119118 defer b.Close()
120119
121− b_url, _ := url.Parse(b.URL)
122− p := testGitLabProvider(b_url.Host)
120+ bURL, _ := url.Parse(b.URL)
121+ p := testGitLabProvider(bURL.Host)
123122
124123 session := &SessionState{AccessToken: "imaginary_access_token"}
125124 email, err := p.GetEmailAddress(session)
providers/google.go+8−3
@@ -20,6 +20,7 @@ import (
2020 "google.golang.org/api/googleapi"
2121)
2222
23+// GoogleProvider represents an Google based Identity Provider
2324type GoogleProvider struct {
2425 *ProviderData
2526 RedeemRefreshURL *url.URL
@@ -28,6 +29,7 @@ type GoogleProvider struct {
2829 GroupValidator func(string) bool
2930}
3031
32+// NewGoogleProvider initiates a new GoogleProvider
3133func NewGoogleProvider(p *ProviderData) *GoogleProvider {
3234 p.ProviderName = "Google"
3335 if p.LoginURL.String() == "" {
@@ -62,7 +64,7 @@ func NewGoogleProvider(p *ProviderData) *GoogleProvider {
6264 }
6365}
6466
65−func emailFromIdToken(idToken string) (string, error) {
67+func emailFromIDToken(idToken string) (string, error) {
6668
6769 // id_token is a base64 encode ID token payload
6870 // https://developers.google.com/accounts/docs/OAuth2Login#obtainuserinfo
@@ -90,6 +92,7 @@ func emailFromIdToken(idToken string) (string, error) {
9092 return email.Email, nil
9193}
9294
95+// Redeem exchanges the OAuth2 authentication token for an ID token
9396func (p *GoogleProvider) Redeem(redirectURL, code string) (s *SessionState, err error) {
9497 if code == "" {
9598 err = errors.New("missing code")
@@ -129,14 +132,14 @@ func (p *GoogleProvider) Redeem(redirectURL, code string) (s *SessionState, err
129132 AccessToken string `json:"access_token"`
130133 RefreshToken string `json:"refresh_token"`
131134 ExpiresIn int64 `json:"expires_in"`
132− IdToken string `json:"id_token"`
135+ IDToken string `json:"id_token"`
133136 }
134137 err = json.Unmarshal(body, &jsonResponse)
135138 if err != nil {
136139 return
137140 }
138141 var email string
139− email, err = emailFromIdToken(jsonResponse.IdToken)
142+ email, err = emailFromIDToken(jsonResponse.IDToken)
140143 if err != nil {
141144 return
142145 }
@@ -249,6 +252,8 @@ func (p *GoogleProvider) ValidateGroup(email string) bool {
249252 return p.GroupValidator(email)
250253}
251254
255+// RefreshSessionIfNeeded checks if the session has expired and uses the
256+// RefreshToken to fetch a new ID token if required
252257func (p *GoogleProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
253258 if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
254259 return false, nil
providers/google_test.go+5−5
@@ -81,7 +81,7 @@ type redeemResponse struct {
8181 AccessToken string `json:"access_token"`
8282 RefreshToken string `json:"refresh_token"`
8383 ExpiresIn int64 `json:"expires_in"`
84− IdToken string `json:"id_token"`
84+ IDToken string `json:"id_token"`
8585}
8686
8787func TestGoogleProviderGetEmailAddress(t *testing.T) {
@@ -90,7 +90,7 @@ func TestGoogleProviderGetEmailAddress(t *testing.T) {
9090 AccessToken: "a1234",
9191 ExpiresIn: 10,
9292 RefreshToken: "refresh12345",
93− IdToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"email": "michael.bland@gsa.gov", "email_verified":true}`)),
93+ IDToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"email": "michael.bland@gsa.gov", "email_verified":true}`)),
9494 })
9595 assert.Equal(t, nil, err)
9696 var server *httptest.Server
@@ -127,7 +127,7 @@ func TestGoogleProviderGetEmailAddressInvalidEncoding(t *testing.T) {
127127 p := newGoogleProvider()
128128 body, err := json.Marshal(redeemResponse{
129129 AccessToken: "a1234",
130− IdToken: "ignored prefix." + `{"email": "michael.bland@gsa.gov"}`,
130+ IDToken: "ignored prefix." + `{"email": "michael.bland@gsa.gov"}`,
131131 })
132132 assert.Equal(t, nil, err)
133133 var server *httptest.Server
@@ -146,7 +146,7 @@ func TestGoogleProviderGetEmailAddressInvalidJson(t *testing.T) {
146146
147147 body, err := json.Marshal(redeemResponse{
148148 AccessToken: "a1234",
149− IdToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"email": michael.bland@gsa.gov}`)),
149+ IDToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"email": michael.bland@gsa.gov}`)),
150150 })
151151 assert.Equal(t, nil, err)
152152 var server *httptest.Server
@@ -165,7 +165,7 @@ func TestGoogleProviderGetEmailAddressEmailMissing(t *testing.T) {
165165 p := newGoogleProvider()
166166 body, err := json.Marshal(redeemResponse{
167167 AccessToken: "a1234",
168− IdToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"not_email": "missing"}`)),
168+ IDToken: "ignored prefix." + base64.URLEncoding.EncodeToString([]byte(`{"not_email": "missing"}`)),
169169 })
170170 assert.Equal(t, nil, err)
171171 var server *httptest.Server
providers/internal_util.go+4−9
@@ -6,7 +6,7 @@ import (
66 "net/http"
77 "net/url"
88
9− "github.com/bitly/oauth2_proxy/api"
9+ "github.com/pusher/oauth2_proxy/api"
1010)
1111
1212// stripToken is a helper function to obfuscate "access_token"
@@ -46,13 +46,13 @@ func stripParam(param, endpoint string) string {
4646}
4747
4848// validateToken returns true if token is valid
49−func validateToken(p Provider, access_token string, header http.Header) bool {
50− if access_token == "" || p.Data().ValidateURL == nil {
49+func validateToken(p Provider, accessToken string, header http.Header) bool {
50+ if accessToken == "" || p.Data().ValidateURL == nil {
5151 return false
5252 }
5353 endpoint := p.Data().ValidateURL.String()
5454 if len(header) == 0 {
55− params := url.Values{"access_token": {access_token}}
55+ params := url.Values{"access_token": {accessToken}}
5656 endpoint = endpoint + "?" + params.Encode()
5757 }
5858 resp, err := api.RequestUnparsedResponse(endpoint, header)
@@ -72,8 +72,3 @@ func validateToken(p Provider, access_token string, header http.Header) bool {
7272 log.Printf("token validation request failed: status %d - %s", resp.StatusCode, body)
7373 return false
7474}
75−
76−func updateURL(url *url.URL, hostname string) {
77− url.Scheme = "http"
78− url.Host = hostname
79−}
providers/internal_util_test.go+47−42
@@ -10,6 +10,11 @@ import (
1010 "github.com/stretchr/testify/assert"
1111)
1212
13+func updateURL(url *url.URL, hostname string) {
14+ url.Scheme = "http"
15+ url.Host = hostname
16+}
17+
1318type ValidateSessionStateTestProvider struct {
1419 *ProviderData
1520}
@@ -25,28 +30,28 @@ func (tp *ValidateSessionStateTestProvider) ValidateSessionState(s *SessionState
2530}
2631
2732type ValidateSessionStateTest struct {
28− backend *httptest.Server
29− response_code int
30− provider *ValidateSessionStateTestProvider
31− header http.Header
33+ backend *httptest.Server
34+ responseCode int
35+ provider *ValidateSessionStateTestProvider
36+ header http.Header
3237}
3338
3439func NewValidateSessionStateTest() *ValidateSessionStateTest {
35− var vt_test ValidateSessionStateTest
40+ var vtTest ValidateSessionStateTest
3641
37− vt_test.backend = httptest.NewServer(
42+ vtTest.backend = httptest.NewServer(
3843 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3944 if r.URL.Path != "/oauth/tokeninfo" {
4045 w.WriteHeader(500)
4146 w.Write([]byte("unknown URL"))
4247 }
43− token_param := r.FormValue("access_token")
44− if token_param == "" {
48+ tokenParam := r.FormValue("access_token")
49+ if tokenParam == "" {
4550 missing := false
46− received_headers := r.Header
47− for k, _ := range vt_test.header {
48− received := received_headers.Get(k)
49− expected := vt_test.header.Get(k)
51+ receivedHeaders := r.Header
52+ for k := range vtTest.header {
53+ received := receivedHeaders.Get(k)
54+ expected := vtTest.header.Get(k)
5055 if received == "" || received != expected {
5156 missing = true
5257 }
@@ -56,68 +61,68 @@ func NewValidateSessionStateTest() *ValidateSessionStateTest {
5661 w.Write([]byte("no token param and missing or incorrect headers"))
5762 }
5863 }
59− w.WriteHeader(vt_test.response_code)
64+ w.WriteHeader(vtTest.responseCode)
6065 w.Write([]byte("only code matters; contents disregarded"))
6166
6267 }))
63− backend_url, _ := url.Parse(vt_test.backend.URL)
64− vt_test.provider = &ValidateSessionStateTestProvider{
68+ backendURL, _ := url.Parse(vtTest.backend.URL)
69+ vtTest.provider = &ValidateSessionStateTestProvider{
6570 ProviderData: &ProviderData{
6671 ValidateURL: &url.URL{
6772 Scheme: "http",
68− Host: backend_url.Host,
73+ Host: backendURL.Host,
6974 Path: "/oauth/tokeninfo",
7075 },
7176 },
7277 }
73− vt_test.response_code = 200
74− return &vt_test
78+ vtTest.responseCode = 200
79+ return &vtTest
7580}
7681
77−func (vt_test *ValidateSessionStateTest) Close() {
78− vt_test.backend.Close()
82+func (vtTest *ValidateSessionStateTest) Close() {
83+ vtTest.backend.Close()
7984}
8085
8186func TestValidateSessionStateValidToken(t *testing.T) {
82− vt_test := NewValidateSessionStateTest()
83− defer vt_test.Close()
84− assert.Equal(t, true, validateToken(vt_test.provider, "foobar", nil))
87+ vtTest := NewValidateSessionStateTest()
88+ defer vtTest.Close()
89+ assert.Equal(t, true, validateToken(vtTest.provider, "foobar", nil))
8590}
8691
8792func TestValidateSessionStateValidTokenWithHeaders(t *testing.T) {
88− vt_test := NewValidateSessionStateTest()
89− defer vt_test.Close()
90− vt_test.header = make(http.Header)
91− vt_test.header.Set("Authorization", "Bearer foobar")
93+ vtTest := NewValidateSessionStateTest()
94+ defer vtTest.Close()
95+ vtTest.header = make(http.Header)
96+ vtTest.header.Set("Authorization", "Bearer foobar")
9297 assert.Equal(t, true,
93− validateToken(vt_test.provider, "foobar", vt_test.header))
98+ validateToken(vtTest.provider, "foobar", vtTest.header))
9499}
95100
96101func TestValidateSessionStateEmptyToken(t *testing.T) {
97− vt_test := NewValidateSessionStateTest()
98− defer vt_test.Close()
99− assert.Equal(t, false, validateToken(vt_test.provider, "", nil))
102+ vtTest := NewValidateSessionStateTest()
103+ defer vtTest.Close()
104+ assert.Equal(t, false, validateToken(vtTest.provider, "", nil))
100105}
101106
102107func TestValidateSessionStateEmptyValidateURL(t *testing.T) {
103− vt_test := NewValidateSessionStateTest()
104− defer vt_test.Close()
105− vt_test.provider.Data().ValidateURL = nil
106− assert.Equal(t, false, validateToken(vt_test.provider, "foobar", nil))
108+ vtTest := NewValidateSessionStateTest()
109+ defer vtTest.Close()
110+ vtTest.provider.Data().ValidateURL = nil
111+ assert.Equal(t, false, validateToken(vtTest.provider, "foobar", nil))
107112}
108113
109114func TestValidateSessionStateRequestNetworkFailure(t *testing.T) {
110− vt_test := NewValidateSessionStateTest()
115+ vtTest := NewValidateSessionStateTest()
111116 // Close immediately to simulate a network failure
112− vt_test.Close()
113− assert.Equal(t, false, validateToken(vt_test.provider, "foobar", nil))
117+ vtTest.Close()
118+ assert.Equal(t, false, validateToken(vtTest.provider, "foobar", nil))
114119}
115120
116121func TestValidateSessionStateExpiredToken(t *testing.T) {
117− vt_test := NewValidateSessionStateTest()
118− defer vt_test.Close()
119− vt_test.response_code = 401
120− assert.Equal(t, false, validateToken(vt_test.provider, "foobar", nil))
122+ vtTest := NewValidateSessionStateTest()
123+ defer vtTest.Close()
124+ vtTest.responseCode = 401
125+ assert.Equal(t, false, validateToken(vtTest.provider, "foobar", nil))
121126}
122127
123128func TestStripTokenNotPresent(t *testing.T) {
providers/linkedin.go+7−3
@@ -6,13 +6,15 @@ import (
66 "net/http"
77 "net/url"
88
9− "github.com/bitly/oauth2_proxy/api"
9+ "github.com/pusher/oauth2_proxy/api"
1010)
1111
12+// LinkedInProvider represents an LinkedIn based Identity Provider
1213type LinkedInProvider struct {
1314 *ProviderData
1415}
1516
17+// NewLinkedInProvider initiates a new LinkedInProvider
1618func NewLinkedInProvider(p *ProviderData) *LinkedInProvider {
1719 p.ProviderName = "LinkedIn"
1820 if p.LoginURL.String() == "" {
@@ -39,14 +41,15 @@ func NewLinkedInProvider(p *ProviderData) *LinkedInProvider {
3941 return &LinkedInProvider{ProviderData: p}
4042}
4143
42−func getLinkedInHeader(access_token string) http.Header {
44+func getLinkedInHeader(accessToken string) http.Header {
4345 header := make(http.Header)
4446 header.Set("Accept", "application/json")
4547 header.Set("x-li-format", "json")
46− header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token))
48+ header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
4749 return header
4850}
4951
52+// GetEmailAddress returns the Account email address
5053func (p *LinkedInProvider) GetEmailAddress(s *SessionState) (string, error) {
5154 if s.AccessToken == "" {
5255 return "", errors.New("missing access token")
@@ -69,6 +72,7 @@ func (p *LinkedInProvider) GetEmailAddress(s *SessionState) (string, error) {
6972 return email, nil
7073}
7174
75+// ValidateSessionState validates the AccessToken
7276func (p *LinkedInProvider) ValidateSessionState(s *SessionState) bool {
7377 return validateToken(p, s.AccessToken, getLinkedInHeader(s.AccessToken))
7478}
providers/linkedin_test.go+7−8
@@ -31,8 +31,7 @@ func testLinkedInBackend(payload string) *httptest.Server {
3131
3232 return httptest.NewServer(http.HandlerFunc(
3333 func(w http.ResponseWriter, r *http.Request) {
34− url := r.URL
35− if url.Path != path {
34+ if r.URL.Path != path {
3635 w.WriteHeader(404)
3736 } else if r.Header.Get("Authorization") != "Bearer imaginary_access_token" {
3837 w.WriteHeader(403)
@@ -95,8 +94,8 @@ func TestLinkedInProviderGetEmailAddress(t *testing.T) {
9594 b := testLinkedInBackend(`"user@linkedin.com"`)
9695 defer b.Close()
9796
98− b_url, _ := url.Parse(b.URL)
99− p := testLinkedInProvider(b_url.Host)
97+ bURL, _ := url.Parse(b.URL)
98+ p := testLinkedInProvider(bURL.Host)
10099
101100 session := &SessionState{AccessToken: "imaginary_access_token"}
102101 email, err := p.GetEmailAddress(session)
@@ -108,8 +107,8 @@ func TestLinkedInProviderGetEmailAddressFailedRequest(t *testing.T) {
108107 b := testLinkedInBackend("unused payload")
109108 defer b.Close()
110109
111− b_url, _ := url.Parse(b.URL)
112− p := testLinkedInProvider(b_url.Host)
110+ bURL, _ := url.Parse(b.URL)
111+ p := testLinkedInProvider(bURL.Host)
113112
114113 // We'll trigger a request failure by using an unexpected access
115114 // token. Alternatively, we could allow the parsing of the payload as
@@ -124,8 +123,8 @@ func TestLinkedInProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
124123 b := testLinkedInBackend("{\"foo\": \"bar\"}")
125124 defer b.Close()
126125
127− b_url, _ := url.Parse(b.URL)
128− p := testLinkedInProvider(b_url.Host)
126+ bURL, _ := url.Parse(b.URL)
127+ p := testLinkedInProvider(bURL.Host)
129128
130129 session := &SessionState{AccessToken: "imaginary_access_token"}
131130 email, err := p.GetEmailAddress(session)
providers/oidc.go+8
@@ -10,17 +10,20 @@ import (
1010 oidc "github.com/coreos/go-oidc"
1111)
1212
13+// OIDCProvider represents an OIDC based Identity Provider
1314type OIDCProvider struct {
1415 *ProviderData
1516
1617 Verifier *oidc.IDTokenVerifier
1718}
1819
20+// NewOIDCProvider initiates a new OIDCProvider
1921func NewOIDCProvider(p *ProviderData) *OIDCProvider {
2022 p.ProviderName = "OpenID Connect"
2123 return &OIDCProvider{ProviderData: p}
2224}
2325
26+// Redeem exchanges the OAuth2 authentication token for an ID token
2427func (p *OIDCProvider) Redeem(redirectURL, code string) (s *SessionState, err error) {
2528 ctx := context.Background()
2629 c := oauth2.Config{
@@ -73,6 +76,11 @@ func (p *OIDCProvider) Redeem(redirectURL, code string) (s *SessionState, err er
7376 return
7477}
7578
79+// RefreshSessionIfNeeded checks if the session has expired and uses the
80+// RefreshToken to fetch a new ID token if required
81+//
82+// WARNGING: This implementation is broken and does not check with the upstream
83+// OIDC provider before refreshing the session
7684func (p *OIDCProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
7785 if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
7886 return false, nil
providers/provider_data.go+3
@@ -4,6 +4,8 @@ import (
44 "net/url"
55)
66
7+// ProviderData contains information required to configure all implementations
8+// of OAuth2 providers
79type ProviderData struct {
810 ProviderName string
911 ClientID string
@@ -17,4 +19,5 @@ type ProviderData struct {
1719 ApprovalPrompt string
1820}
1921
22+// Data returns the ProviderData
2023func (p *ProviderData) Data() *ProviderData { return p }
providers/provider_default.go+6−2
@@ -9,9 +9,10 @@ import (
99 "net/http"
1010 "net/url"
1111
12− "github.com/bitly/oauth2_proxy/cookie"
12+ "github.com/pusher/oauth2_proxy/cookie"
1313)
1414
15+// Redeem provides a default implementation of the OAuth2 token redemption process
1516func (p *ProviderData) Redeem(redirectURL, code string) (s *SessionState, err error) {
1617 if code == "" {
1718 err = errors.New("missing code")
@@ -102,6 +103,7 @@ func (p *ProviderData) SessionFromCookie(v string, c *cookie.Cipher) (s *Session
102103 return DecodeSessionState(v, c)
103104}
104105
106+// GetEmailAddress returns the Account email address
105107func (p *ProviderData) GetEmailAddress(s *SessionState) (string, error) {
106108 return "", errors.New("not implemented")
107109}
@@ -117,11 +119,13 @@ func (p *ProviderData) ValidateGroup(email string) bool {
117119 return true
118120}
119121
122+// ValidateSessionState validates the AccessToken
120123func (p *ProviderData) ValidateSessionState(s *SessionState) bool {
121124 return validateToken(p, s.AccessToken, nil)
122125}
123126
124−// RefreshSessionIfNeeded
127+// RefreshSessionIfNeeded should refresh the user's session if required and
128+// do nothing if a refresh is not required
125129func (p *ProviderData) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
126130 return false, nil
127131}
providers/providers.go+3−1
@@ -1,9 +1,10 @@
11package providers
22
33import (
4− "github.com/bitly/oauth2_proxy/cookie"
4+ "github.com/pusher/oauth2_proxy/cookie"
55)
66
7+// Provider represents an upstream identity provider implementation
78type Provider interface {
89 Data() *ProviderData
910 GetEmailAddress(*SessionState) (string, error)
@@ -17,6 +18,7 @@ type Provider interface {
1718 CookieForSession(*SessionState, *cookie.Cipher) (string, error)
1819}
1920
21+// New provides a new Provider based on the configured provider string
2022func New(provider string, p *ProviderData) Provider {
2123 switch provider {
2224 case "linkedin":
providers/session_state.go+7−1
@@ -6,9 +6,10 @@ import (
66 "strings"
77 "time"
88
9− "github.com/bitly/oauth2_proxy/cookie"
9+ "github.com/pusher/oauth2_proxy/cookie"
1010)
1111
12+// SessionState is used to store information about the currently authenticated user session
1213type SessionState struct {
1314 AccessToken string
1415 ExpiresOn time.Time
@@ -17,6 +18,7 @@ type SessionState struct {
1718 User string
1819}
1920
21+// IsExpired checks whether the session has expired
2022func (s *SessionState) IsExpired() bool {
2123 if !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(time.Now()) {
2224 return true
@@ -24,6 +26,7 @@ func (s *SessionState) IsExpired() bool {
2426 return false
2527}
2628
29+// String constructs a summary of the session state
2730func (s *SessionState) String() string {
2831 o := fmt.Sprintf("Session{%s", s.accountInfo())
2932 if s.AccessToken != "" {
@@ -38,6 +41,7 @@ func (s *SessionState) String() string {
3841 return o + "}"
3942}
4043
44+// EncodeSessionState returns string representation of the current session
4145func (s *SessionState) EncodeSessionState(c *cookie.Cipher) (string, error) {
4246 if c == nil || s.AccessToken == "" {
4347 return s.accountInfo(), nil
@@ -49,6 +53,7 @@ func (s *SessionState) accountInfo() string {
4953 return fmt.Sprintf("email:%s user:%s", s.Email, s.User)
5054}
5155
56+// EncryptedString encrypts the session state into a cookie string
5257func (s *SessionState) EncryptedString(c *cookie.Cipher) (string, error) {
5358 var err error
5459 if c == nil {
@@ -84,6 +89,7 @@ func decodeSessionStatePlain(v string) (s *SessionState, err error) {
8489 return &SessionState{User: user, Email: email}, nil
8590}
8691
92+// DecodeSessionState decodes the session cookie string into a SessionState
8793func DecodeSessionState(v string, c *cookie.Cipher) (s *SessionState, err error) {
8894 if c == nil {
8995 return decodeSessionStatePlain(v)
providers/session_state_test.go+1−1
@@ -6,7 +6,7 @@ import (
66 "testing"
77 "time"
88
9− "github.com/bitly/oauth2_proxy/cookie"
9+ "github.com/pusher/oauth2_proxy/cookie"
1010 "github.com/stretchr/testify/assert"
1111)
1212
string_array.go+3
@@ -4,13 +4,16 @@ import (
44 "strings"
55)
66
7+// StringArray is a type alias for a slice of strings
78type StringArray []string
89
10+// Set appends a string to the StringArray
911func (a *StringArray) Set(s string) error {
1012 *a = append(*a, s)
1113 return nil
1214}
1315
16+// String joins elements of the StringArray into a single comma separated string
1417func (a *StringArray) String() string {
1518 return strings.Join(*a, ",")
1619}
templates.go+1−1
@@ -142,7 +142,7 @@ func getTemplates() *template.Template {
142142 <footer>
143143 {{ if eq .Footer "-" }}
144144 {{ else if eq .Footer ""}}
145− Secured with <a href="https://github.com/bitly/oauth2_proxy#oauth2_proxy">OAuth2 Proxy</a> version {{.Version}}
145+ Secured with <a href="https://github.com/pusher/oauth2_proxy#oauth2_proxy">OAuth2 Proxy</a> version {{.Version}}
146146 {{ else }}
147147 {{.Footer}}
148148 {{ end }}
test.shdeleted−14
@@ -1,14 +0,0 @@
1−#!/bin/bash
2−EXIT_CODE=0
3−echo "gofmt"
4−diff -u <(echo -n) <(gofmt -d $(find . -type f -name '*.go' -not -path "./vendor/*")) || EXIT_CODE=1
5−for pkg in $(go list ./... | grep -v '/vendor/' ); do
6− echo "testing $pkg"
7− echo "go vet $pkg"
8− go vet "$pkg" || EXIT_CODE=1
9− echo "go test -v $pkg"
10− go test -v -timeout 90s "$pkg" || EXIT_CODE=1
11− echo "go test -v -race $pkg"
12− GOMAXPROCS=4 go test -v -timeout 90s0s -race "$pkg" || EXIT_CODE=1
13−done
14−exit $EXIT_CODE
validator.go+11−5
@@ -10,11 +10,13 @@ import (
1010 "unsafe"
1111)
1212
13+// UserMap holds information from the authenticated emails file
1314type UserMap struct {
1415 usersFile string
1516 m unsafe.Pointer
1617}
1718
19+// NewUserMap parses the authenticated emails file into a new UserMap
1820func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
1921 um := &UserMap{usersFile: usersFile}
2022 m := make(map[string]bool)
@@ -30,23 +32,26 @@ func NewUserMap(usersFile string, done <-chan bool, onUpdate func()) *UserMap {
3032 return um
3133}
3234
35+// IsValid checks if an email is allowed
3336func (um *UserMap) IsValid(email string) (result bool) {
3437 m := *(*map[string]bool)(atomic.LoadPointer(&um.m))
3538 _, result = m[email]
3639 return
3740}
3841
42+// LoadAuthenticatedEmailsFile loads the authenticated emails file from disk
43+// and parses the contents as CSV
3944func (um *UserMap) LoadAuthenticatedEmailsFile() {
4045 r, err := os.Open(um.usersFile)
4146 if err != nil {
4247 log.Fatalf("failed opening authenticated-emails-file=%q, %s", um.usersFile, err)
4348 }
4449 defer r.Close()
45− csv_reader := csv.NewReader(r)
46− csv_reader.Comma = ','
47− csv_reader.Comment = '#'
48− csv_reader.TrimLeadingSpace = true
49− records, err := csv_reader.ReadAll()
50+ csvReader := csv.NewReader(r)
51+ csvReader.Comma = ','
52+ csvReader.Comment = '#'
53+ csvReader.TrimLeadingSpace = true
54+ records, err := csvReader.ReadAll()
5055 if err != nil {
5156 log.Printf("error reading authenticated-emails-file=%q, %s", um.usersFile, err)
5257 return
@@ -91,6 +96,7 @@ func newValidatorImpl(domains []string, usersFile string,
9196 return validator
9297}
9398
99+// NewValidator constructs a function to validate email addresses
94100func NewValidator(domains []string, usersFile string) func(string) bool {
95101 return newValidatorImpl(domains, usersFile, nil, func() {})
96102}
validator_test.go+13−13
@@ -8,15 +8,15 @@ import (
88)
99
1010type ValidatorTest struct {
11− auth_email_file *os.File
12− done chan bool
13− update_seen bool
11+ authEmailFile *os.File
12+ done chan bool
13+ updateSeen bool
1414}
1515
1616func NewValidatorTest(t *testing.T) *ValidatorTest {
1717 vt := &ValidatorTest{}
1818 var err error
19− vt.auth_email_file, err = ioutil.TempFile("", "test_auth_emails_")
19+ vt.authEmailFile, err = ioutil.TempFile("", "test_auth_emails_")
2020 if err != nil {
2121 t.Fatal("failed to create temp file: " + err.Error())
2222 }
@@ -26,27 +26,27 @@ func NewValidatorTest(t *testing.T) *ValidatorTest {
2626
2727func (vt *ValidatorTest) TearDown() {
2828 vt.done <- true
29− os.Remove(vt.auth_email_file.Name())
29+ os.Remove(vt.authEmailFile.Name())
3030}
3131
3232func (vt *ValidatorTest) NewValidator(domains []string,
3333 updated chan<- bool) func(string) bool {
34− return newValidatorImpl(domains, vt.auth_email_file.Name(),
34+ return newValidatorImpl(domains, vt.authEmailFile.Name(),
3535 vt.done, func() {
36− if vt.update_seen == false {
36+ if vt.updateSeen == false {
3737 updated <- true
38− vt.update_seen = true
38+ vt.updateSeen = true
3939 }
4040 })
4141}
4242
43−// This will close vt.auth_email_file.
43+// This will close vt.authEmailFile.
4444func (vt *ValidatorTest) WriteEmails(t *testing.T, emails []string) {
45− defer vt.auth_email_file.Close()
46− vt.auth_email_file.WriteString(strings.Join(emails, "\n"))
47− if err := vt.auth_email_file.Close(); err != nil {
45+ defer vt.authEmailFile.Close()
46+ vt.authEmailFile.WriteString(strings.Join(emails, "\n"))
47+ if err := vt.authEmailFile.Close(); err != nil {
4848 t.Fatal("failed to close temp file " +
49− vt.auth_email_file.Name() + ": " + err.Error())
49+ vt.authEmailFile.Name() + ": " + err.Error())
5050 }
5151}
5252
validator_watcher_copy_test.go+4−4
@@ -12,18 +12,18 @@ import (
1212
1313func (vt *ValidatorTest) UpdateEmailFileViaCopyingOver(
1414 t *testing.T, emails []string) {
15− orig_file := vt.auth_email_file
15+ origFile := vt.authEmailFile
1616 var err error
17− vt.auth_email_file, err = ioutil.TempFile("", "test_auth_emails_")
17+ vt.authEmailFile, err = ioutil.TempFile("", "test_auth_emails_")
1818 if err != nil {
1919 t.Fatal("failed to create temp file for copy: " + err.Error())
2020 }
2121 vt.WriteEmails(t, emails)
22− err = os.Rename(vt.auth_email_file.Name(), orig_file.Name())
22+ err = os.Rename(vt.authEmailFile.Name(), origFile.Name())
2323 if err != nil {
2424 t.Fatal("failed to copy over temp file: " + err.Error())
2525 }
26− vt.auth_email_file = orig_file
26+ vt.authEmailFile = origFile
2727}
2828
2929func TestValidatorOverwriteEmailListViaCopyingOver(t *testing.T) {
validator_watcher_test.go+9−9
@@ -10,8 +10,8 @@ import (
1010
1111func (vt *ValidatorTest) UpdateEmailFile(t *testing.T, emails []string) {
1212 var err error
13− vt.auth_email_file, err = os.OpenFile(
14− vt.auth_email_file.Name(), os.O_WRONLY|os.O_CREATE, 0600)
13+ vt.authEmailFile, err = os.OpenFile(
14+ vt.authEmailFile.Name(), os.O_WRONLY|os.O_CREATE, 0600)
1515 if err != nil {
1616 t.Fatal("failed to re-open temp file for updates")
1717 }
@@ -20,24 +20,24 @@ func (vt *ValidatorTest) UpdateEmailFile(t *testing.T, emails []string) {
2020
2121func (vt *ValidatorTest) UpdateEmailFileViaRenameAndReplace(
2222 t *testing.T, emails []string) {
23− orig_file := vt.auth_email_file
23+ origFile := vt.authEmailFile
2424 var err error
25− vt.auth_email_file, err = ioutil.TempFile("", "test_auth_emails_")
25+ vt.authEmailFile, err = ioutil.TempFile("", "test_auth_emails_")
2626 if err != nil {
2727 t.Fatal("failed to create temp file for rename and replace: " +
2828 err.Error())
2929 }
3030 vt.WriteEmails(t, emails)
3131
32− moved_name := orig_file.Name() + "-moved"
33− err = os.Rename(orig_file.Name(), moved_name)
34− err = os.Rename(vt.auth_email_file.Name(), orig_file.Name())
32+ movedName := origFile.Name() + "-moved"
33+ err = os.Rename(origFile.Name(), movedName)
34+ err = os.Rename(vt.authEmailFile.Name(), origFile.Name())
3535 if err != nil {
3636 t.Fatal("failed to rename and replace temp file: " +
3737 err.Error())
3838 }
39− vt.auth_email_file = orig_file
40− os.Remove(moved_name)
39+ vt.authEmailFile = origFile
40+ os.Remove(movedName)
4141}
4242
4343func TestValidatorOverwriteEmailListDirectly(t *testing.T) {
version.go+2−1
@@ -1,3 +1,4 @@
11package main
22
3−const VERSION = "2.2.1-alpha"
3+// VERSION contains version information
4+var VERSION = "undefined"
watcher.go+8−5
@@ -8,16 +8,18 @@ import (
88 "path/filepath"
99 "time"
1010
11− "gopkg.in/fsnotify.v1"
11+ fsnotify "gopkg.in/fsnotify/fsnotify.v1"
1212)
1313
14+// WaitForReplacement waits for a file to exist on disk and then starts a watch
15+// for the file
1416func WaitForReplacement(filename string, op fsnotify.Op,
1517 watcher *fsnotify.Watcher) {
16− const sleep_interval = 50 * time.Millisecond
18+ const sleepInterval = 50 * time.Millisecond
1719
1820 // Avoid a race when fsnofity.Remove is preceded by fsnotify.Chmod.
1921 if op&fsnotify.Chmod != 0 {
20− time.Sleep(sleep_interval)
22+ time.Sleep(sleepInterval)
2123 }
2224 for {
2325 if _, err := os.Stat(filename); err == nil {
@@ -26,10 +28,11 @@ func WaitForReplacement(filename string, op fsnotify.Op,
2628 return
2729 }
2830 }
29− time.Sleep(sleep_interval)
31+ time.Sleep(sleepInterval)
3032 }
3133}
3234
35+// WatchForUpdates performs an action every time a file on disk is updated
3336func WatchForUpdates(filename string, done <-chan bool, action func()) {
3437 filename = filepath.Clean(filename)
3538 watcher, err := fsnotify.NewWatcher()
@@ -56,7 +59,7 @@ func WatchForUpdates(filename string, done <-chan bool, action func()) {
5659 }
5760 log.Printf("reloading after event: %s", event)
5861 action()
59− case err := <-watcher.Errors:
62+ case err = <-watcher.Errors:
6063 log.Printf("error watching %s: %s", filename, err)
6164 }
6265 }