1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-04-23 12:18:50 +02:00

Merge branch 'master' into feat/static-upstream

This commit is contained in:
Christian Groschupp 2019-11-01 17:34:27 +01:00 committed by GitHub
commit 9e4a7ee84e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 323 additions and 263 deletions

1
.gitignore vendored
View File

@ -16,6 +16,7 @@ release
# Folders # Folders
_obj _obj
_test _test
.idea/
# Architecture specific extensions/prefixes # Architecture specific extensions/prefixes
*.[568vq] *.[568vq]

View File

@ -1,6 +1,5 @@
language: go language: go
go: go:
- 1.12.x
- 1.13.x - 1.13.x
install: install:
# Fetch dependencies # Fetch dependencies

View File

@ -1,10 +1,16 @@
# Vx.x.x (Pre-release) # Vx.x.x (Pre-release)
## Changes since v4.0.0 ## Changes since v4.0.0
- [#292](https://github.com/pusher/oauth2_proxy/pull/292) Added bash >= 4.0 dependency to configure script (@jmfrank63)
- [#227](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka) - [#227](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka)
- [#259](https://github.com/pusher/oauth2_proxy/pull/259) Redirect to HTTPS (@jmickey)
- [#273](https://github.com/pusher/oauth2_proxy/pull/273) Support Go 1.13 (@dio) - [#273](https://github.com/pusher/oauth2_proxy/pull/273) Support Go 1.13 (@dio)
- [#275](https://github.com/pusher/oauth2_proxy/pull/275) docker: build from debian buster (@syscll) - [#275](https://github.com/pusher/oauth2_proxy/pull/275) docker: build from debian buster (@syscll)
- [#258](https://github.com/pusher/oauth2_proxy/pull/258) Add IDToken for Azure provider
- This PR adds the IDToken into the session for the Azure provider allowing requests to a backend to be identified as a specific user. As a consequence, if you are using a cookie to store the session the cookie will now exceed the 4kb size limit and be split into multiple cookies. This can cause problems when using nginx as a proxy, resulting in no cookie being passed at all. Either increase the proxy_buffer_size in nginx or implement the redis session storage (see https://pusher.github.io/oauth2_proxy/configuration#redis-storage)
- [#286](https://github.com/pusher/oauth2_proxy/pull/286) Requests.go updated with useful error messages (@biotom)
- [#302](https://github.com/pusher/oauth2_proxy/pull/302) Rewrite dist script (@syscll)
- [#304](https://github.com/pusher/oauth2_proxy/pull/304) Add new Logo! :tada: (@JoelSpeed)
- [#265](https://github.com/pusher/oauth2_proxy/pull/265) Add upstream with static response (@cgroschupp) - [#265](https://github.com/pusher/oauth2_proxy/pull/265) Add upstream with static response (@cgroschupp)
# v4.0.0 # v4.0.0

View File

@ -61,29 +61,4 @@ test: lint
.PHONY: release .PHONY: release
release: lint test release: lint test
mkdir release BINARY=${BINARY} VERSION=${VERSION} ./dist.sh
mkdir release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)
mkdir release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)
mkdir release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)
mkdir release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)
mkdir release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)
GO111MODULE=on GOOS=darwin GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \
-o release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
GO111MODULE=on GOOS=linux GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \
-o release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
GO111MODULE=on GOOS=linux GOARCH=arm64 go build -ldflags="-X main.VERSION=${VERSION}" \
-o release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
GO111MODULE=on GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X main.VERSION=${VERSION}" \
-o release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
GO111MODULE=on GOOS=windows GOARCH=amd64 go build -ldflags="-X main.VERSION=${VERSION}" \
-o release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)/$(BINARY) github.com/pusher/oauth2_proxy
shasum -a 256 release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).darwin-amd64-sha256sum.txt
shasum -a 256 release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-amd64-sha256sum.txt
shasum -a 256 release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-arm64-sha256sum.txt
shasum -a 256 release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).linux-armv6-sha256sum.txt
shasum -a 256 release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)/$(BINARY) > release/$(BINARY)-$(VERSION).windows-amd64-sha256sum.txt
tar -C release -czvf release/$(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).darwin-amd64.$(GO_VERSION)
tar -C release -czvf release/$(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).linux-amd64.$(GO_VERSION)
tar -C release -czvf release/$(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).linux-arm64.$(GO_VERSION)
tar -C release -czvf release/$(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).linux-armv6.$(GO_VERSION)
tar -C release -czvf release/$(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION).tar.gz $(BINARY)-$(VERSION).windows-amd64.$(GO_VERSION)

View File

@ -1,4 +1,9 @@
# oauth2_proxy ![OAuth2 Proxy](/docs/logos/OAuth2_Proxy_horizontal.svg)
[![Build Status](https://secure.travis-ci.org/pusher/oauth2_proxy.svg?branch=master)](http://travis-ci.org/pusher/oauth2_proxy)
[![Go Report Card](https://goreportcard.com/badge/github.com/pusher/oauth2_proxy)](https://goreportcard.com/report/github.com/pusher/oauth2_proxy)
[![GoDoc](https://godoc.org/github.com/pusher/oauth2_proxy?status.svg)](https://godoc.org/github.com/pusher/oauth2_proxy)
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
A reverse proxy and static file server that provides authentication using Providers (Google, GitHub, and others) A reverse proxy and static file server that provides authentication using Providers (Google, GitHub, and others)
to validate accounts by email, domain or group. to validate accounts by email, domain or group.
@ -7,8 +12,6 @@ to validate accounts by email, domain or group.
Versions v3.0.0 and up are from this fork and will have diverged from any changes in the original fork. Versions v3.0.0 and up are from this fork and will have diverged from any changes in the original fork.
A list of changes can be seen in the [CHANGELOG](CHANGELOG.md). A list of changes can be seen in the [CHANGELOG](CHANGELOG.md).
[![Build Status](https://secure.travis-ci.org/pusher/oauth2_proxy.svg?branch=master)](http://travis-ci.org/pusher/oauth2_proxy)
![Sign In Page](https://cloud.githubusercontent.com/assets/45028/4970624/7feb7dd8-6886-11e4-93e0-c9904af44ea8.png) ![Sign In Page](https://cloud.githubusercontent.com/assets/45028/4970624/7feb7dd8-6886-11e4-93e0-c9904af44ea8.png)
## Installation ## Installation

4
configure vendored
View File

@ -5,6 +5,10 @@ GREEN='\033[0;32m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
NC='\033[0m' NC='\033[0m'
if [ -z "${BASH_VERSINFO}" ] || [ -z "${BASH_VERSINFO[0]}" ] || [ ${BASH_VERSINFO[0]} -lt 4 ]; then
echo "This script requires Bash version >= 4"; exit 1;
fi
declare -A tools=() declare -A tools=()
declare -A desired=() declare -A desired=()

83
dist.sh
View File

@ -1,45 +1,46 @@
#!/bin/bash #!/usr/bin/env bash
# build binary distributions for linux/amd64 and darwin/amd64
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" set -o errexit
echo "working dir $DIR"
mkdir -p $DIR/dist
dep ensure || exit 1
os=$(go env GOOS) if [[ -z ${BINARY} ]] || [[ -z ${VERSION} ]]; then
arch=$(go env GOARCH) echo "Missing required env var: BINARY=X VERSION=X $(basename $0)"
version=$(cat $DIR/version.go | grep "const VERSION" | awk '{print $NF}' | sed 's/"//g') exit 1
goversion=$(go version | awk '{print $3}')
sha256sum=()
echo "... running tests"
./test.sh
for os in windows linux darwin; do
echo "... building v$version for $os/$arch"
EXT=
if [ $os = windows ]; then
EXT=".exe"
fi
BUILD=$(mktemp -d ${TMPDIR:-/tmp}/oauth2_proxy.XXXXXX)
TARGET="oauth2_proxy-$version.$os-$arch.$goversion"
FILENAME="oauth2_proxy-$version.$os-$arch$EXT"
GOOS=$os GOARCH=$arch CGO_ENABLED=0 \
go build -ldflags="-s -w" -o $BUILD/$TARGET/$FILENAME || exit 1
pushd $BUILD/$TARGET
sha256sum+=("$(shasum -a 256 $FILENAME || exit 1)")
cd .. && tar czvf $TARGET.tar.gz $TARGET
mv $TARGET.tar.gz $DIR/dist
popd
done
checksum_file="sha256sum.txt"
cd $DIR/dist
if [ -f $checksum_file ]; then
rm $checksum_file
fi fi
touch $checksum_file
for checksum in "${sha256sum[@]}"; do # Check for Go version 1.13.*
echo "$checksum" >> $checksum_file GO_VERSION=$(go version | awk '{print $3}')
if [[ ! "${GO_VERSION}" =~ ^go1.13.* ]]; then
echo "Go version must be >= go1.13"
exit 1
fi
ARCHS=(darwin-amd64 linux-amd64 linux-arm64 linux-armv6 windows-amd64)
mkdir -p release
# Create architecture specific release dirs
for ARCH in "${ARCHS[@]}"; do
mkdir -p release/${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}
GO_OS=$(echo $ARCH | awk -F- '{print $1}')
GO_ARCH=$(echo $ARCH | awk -F- '{print $2}')
# Create architecture specific binaries
if [[ ${GO_ARCH} == "armv6" ]]; then
GO111MODULE=on GOOS=${GO_OS} GOARCH=arm GOARM=6 go build -ldflags="-X main.VERSION=${VERSION}" \
-o release/${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}/${BINARY} github.com/pusher/oauth2_proxy
else
GO111MODULE=on GOOS=${GO_OS} GOARCH=${GO_ARCH} go build -ldflags="-X main.VERSION=${VERSION}" \
-o release/${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}/${BINARY} github.com/pusher/oauth2_proxy
fi
cd release
# Create sha256sum for architecture specific binary
shasum -a 256 ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}/${BINARY} > ${BINARY}-${VERSION}.${ARCH}-sha256sum.txt
# Create tar file for architecture specific binary
tar -czvf ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}.tar.gz ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}
cd ..
done done

View File

@ -5,7 +5,7 @@ permalink: /
nav_order: 0 nav_order: 0
--- ---
# oauth2_proxy ![OAuth2 Proxy](/logos/OAuth2_Proxy_horizontal.svg)
A reverse proxy and static file server that provides authentication using Providers (Google, GitHub, and others) A reverse proxy and static file server that provides authentication using Providers (Google, GitHub, and others)
to validate accounts by email, domain or group. to validate accounts by email, domain or group.

View File

@ -9,7 +9,7 @@ nav_order: 1
1. Choose how to deploy: 1. Choose how to deploy:
a. Download [Prebuilt Binary](https://github.com/pusher/oauth2_proxy/releases) (current release is `v3.2.0`) a. Download [Prebuilt Binary](https://github.com/pusher/oauth2_proxy/releases) (current release is `v4.0.0`)
b. Build with `$ go get github.com/pusher/oauth2_proxy` which will put the binary in `$GOROOT/bin` b. Build with `$ go get github.com/pusher/oauth2_proxy` which will put the binary in `$GOROOT/bin`
@ -18,8 +18,8 @@ nav_order: 1
Prebuilt binaries can be validated by extracting the file and verifying it against the `sha256sum.txt` checksum file provided for each release starting with version `v3.0.0`. Prebuilt binaries can be validated by extracting the file and verifying it against the `sha256sum.txt` checksum file provided for each release starting with version `v3.0.0`.
``` ```
sha256sum -c sha256sum.txt 2>&1 | grep OK $ sha256sum -c sha256sum.txt 2>&1 | grep OK
oauth2_proxy-3.2.0.linux-amd64: OK oauth2_proxy-4.0.0.linux-amd64: OK
``` ```
2. [Select a Provider and Register an OAuth Application with a Provider](auth-configuration) 2. [Select a Provider and Register an OAuth Application with a Provider](auth-configuration)

View File

@ -81,6 +81,8 @@ Note: The user is checked against the group members list on initial authenticati
--client-secret=<value from step 6> --client-secret=<value from step 6>
``` ```
Note: When using the Azure Auth provider with nginx and the cookie session store you may find the cookie is too large and doesn't get passed through correctly. Increasing the proxy_buffer_size in nginx or implementing the [redis session storage](configuration#redis-storage) should resolve this.
### Facebook Auth Provider ### Facebook Auth Provider
1. Create a new FB App from <https://developers.facebook.com/> 1. Create a new FB App from <https://developers.facebook.com/>

View File

@ -14,6 +14,7 @@
# You can create any custom variable you would like, and they will be accessible # You can create any custom variable you would like, and they will be accessible
# in the templates via {{ site.myvariable }}. # in the templates via {{ site.myvariable }}.
title: OAuth2_Proxy title: OAuth2_Proxy
logo: /logos/OAuth2_Proxy_horizontal.svg
description: >- # this means to ignore newlines until "baseurl:" description: >- # this means to ignore newlines until "baseurl:"
OAuth2_Proxy documentation site OAuth2_Proxy documentation site
baseurl: "/oauth2_proxy" # the subpath of your site, e.g. /blog baseurl: "/oauth2_proxy" # the subpath of your site, e.g. /blog

View File

@ -44,6 +44,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example
| `-extra-jwt-issuers` | string | if `-skip-jwt-bearer-tokens` is set, a list of extra JWT `issuer=audience` pairs (where the issuer URL has a `.well-known/openid-configuration` or a `.well-known/jwks.json`) | | | `-extra-jwt-issuers` | string | if `-skip-jwt-bearer-tokens` is set, a list of extra JWT `issuer=audience` pairs (where the issuer URL has a `.well-known/openid-configuration` or a `.well-known/jwks.json`) | |
| `-exclude-logging-paths` | string | comma separated list of paths to exclude from logging, eg: `"/ping,/path2"` |`""` (no paths excluded) | | `-exclude-logging-paths` | string | comma separated list of paths to exclude from logging, eg: `"/ping,/path2"` |`""` (no paths excluded) |
| `-flush-interval` | duration | period between flushing response buffers when streaming responses | `"1s"` | | `-flush-interval` | duration | period between flushing response buffers when streaming responses | `"1s"` |
| `-force-https` | bool | enforce https redirect | `false` |
| `-banner` | string | custom banner string. Use `"-"` to disable default banner. | | | `-banner` | string | custom banner string. Use `"-"` to disable default banner. | |
| `-footer` | string | custom footer string. Use `"-"` to disable default footer. | | | `-footer` | string | custom footer string. Use `"-"` to disable default footer. | |
| `-gcp-healthchecks` | bool | will enable `/liveness_check`, `/readiness_check`, and `/` (with the proper user-agent) endpoints that will make it work well with GCP App Engine and GKE Ingresses | false | | `-gcp-healthchecks` | bool | will enable `/liveness_check`, `/readiness_check`, and `/` (with the proper user-agent) endpoints that will make it work well with GCP App Engine and GKE Ingresses | false |
@ -116,7 +117,7 @@ See below for provider specific options
### Upstreams Configuration ### Upstreams Configuration
`oauth2_proxy` supports having multiple upstreams, and has the option to pass requests on to HTTP(S) servers or serve static files from the file system. HTTP and HTTPS upstreams are configured by providing a URL such as `http://127.0.0.1:8080/` for the upstream parameter, that will forward all authenticated requests to be forwarded to the upstream server. If you instead provide `http://127.0.0.1:8080/some/path/` then it will only be requests that start with `/some/path/` which are forwarded to the upstream. `oauth2_proxy` supports having multiple upstreams, and has the option to pass requests on to HTTP(S) servers or serve static files from the file system. HTTP and HTTPS upstreams are configured by providing a URL such as `http://127.0.0.1:8080/` for the upstream parameter, this will forward all authenticated requests to the upstream server. If you instead provide `http://127.0.0.1:8080/some/path/` then it will only be requests that start with `/some/path/` which are forwarded to the upstream.
Static file paths are configured as a file:// URL. `file:///var/www/static/` will serve the files from that directory at `http://[oauth2_proxy url]/var/www/static/`, which may not be what you want. You can provide the path to where the files should be available by adding a fragment to the configured URL. The value of the fragment will then be used to specify which path the files are available at. `file:///var/www/static/#/static/` will ie. make `/var/www/static/` available at `http://[oauth2_proxy url]/static/`. Static file paths are configured as a file:// URL. `file:///var/www/static/` will serve the files from that directory at `http://[oauth2_proxy url]/var/www/static/`, which may not be what you want. You can provide the path to where the files should be available by adding a fragment to the configured URL. The value of the fragment will then be used to specify which path the files are available at. `file:///var/www/static/#/static/` will ie. make `/var/www/static/` available at `http://[oauth2_proxy url]/static/`.

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 360"><defs><style>.cls-1{fill:#06ed94;}.cls-2{fill:#059b61;}.cls-3{fill:#1957ba;}.cls-4{fill:#6ca8ff;}</style></defs><title>OAuth2_Proxy_logo_v3</title><path class="cls-1" d="M179.00205,349.728c-93.69483,0-169.921-76.22687-169.921-169.9217S85.30722,9.88462,179.00205,9.88462c93.69408,0,169.92021,76.22687,169.92021,169.9217S272.69613,349.728,179.00205,349.728Zm0-327.81581c-87.06238,0-157.89338,70.831-157.89338,157.89411s70.831,157.89411,157.89338,157.89411,157.89411-70.831,157.89411-157.89411S266.06442,21.91221,179.00205,21.91221Z"/><polygon class="cls-2" points="208.628 90.373 203.724 108.705 243.153 141.064 280.21 136.494 208.628 90.373"/><polygon class="cls-1" points="304.604 167.593 304.604 133.558 250.778 87.759 208.628 90.373 260.02 131.65 53.401 131.65 53.401 169.893 304.604 167.593"/><polygon class="cls-3" points="149.376 261.504 154.28 243.172 113.684 209.179 77.795 215.382 149.376 261.504"/><polygon class="cls-4" points="53.401 184.283 53.401 218.319 107.226 264.118 149.376 261.504 97.984 220.226 304.604 220.226 304.604 181.984 53.401 184.283"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

11
http.go
View File

@ -152,3 +152,14 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc.SetKeepAlivePeriod(3 * time.Minute) tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil return tc, nil
} }
func redirectToHTTPS(opts *Options, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
proto := r.Header.Get("X-Forwarded-Proto")
if opts.ForceHTTPS && (r.TLS == nil || (proto != "" && strings.ToLower(proto) != "https")) {
http.Redirect(w, r, opts.HTTPSAddress, http.StatusPermanentRedirect)
}
h.ServeHTTP(w, r)
})
}

View File

@ -106,3 +106,53 @@ func TestGCPHealthcheckNotIngressPut(t *testing.T) {
assert.Equal(t, "test", rw.Body.String()) assert.Equal(t, "test", rw.Body.String())
} }
func TestRedirectToHTTPSTrue(t *testing.T) {
opts := NewOptions()
opts.ForceHTTPS = true
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := redirectToHTTPS(opts, http.HandlerFunc(handler))
rw := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
h.ServeHTTP(rw, r)
assert.Equal(t, http.StatusPermanentRedirect, rw.Code, "status code should be %d, got: %d", http.StatusPermanentRedirect, rw.Code)
}
func TestRedirectToHTTPSFalse(t *testing.T) {
opts := NewOptions()
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := redirectToHTTPS(opts, http.HandlerFunc(handler))
rw := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
h.ServeHTTP(rw, r)
assert.Equal(t, http.StatusOK, rw.Code, "status code should be %d, got: %d", http.StatusOK, rw.Code)
}
func TestRedirectNotWhenHTTPS(t *testing.T) {
opts := NewOptions()
opts.ForceHTTPS = true
handler := func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("test"))
}
h := redirectToHTTPS(opts, http.HandlerFunc(handler))
s := httptest.NewTLSServer(h)
defer s.Close()
opts.HTTPSAddress = s.URL
client := s.Client()
res, err := client.Get(s.URL)
if err != nil {
t.Fatalf("request to test server failed with error: %v", err)
}
assert.Equal(t, http.StatusOK, res.StatusCode, "status code should be %d, got: %d", http.StatusOK, res.StatusCode)
}

View File

@ -32,6 +32,7 @@ func main() {
flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients") flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients")
flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients") flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients")
flagSet.Bool("force-https", false, "force HTTPS redirect for HTTP requests")
flagSet.String("tls-cert-file", "", "path to certificate file") flagSet.String("tls-cert-file", "", "path to certificate file")
flagSet.String("tls-key-file", "", "path to private key file") flagSet.String("tls-key-file", "", "path to private key file")
flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"") flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
@ -185,9 +186,9 @@ func main() {
var handler http.Handler var handler http.Handler
if opts.GCPHealthChecks { if opts.GCPHealthChecks {
handler = gcpHealthcheck(LoggingHandler(oauthproxy)) handler = redirectToHTTPS(opts, gcpHealthcheck(LoggingHandler(oauthproxy)))
} else { } else {
handler = LoggingHandler(oauthproxy) handler = redirectToHTTPS(opts, LoggingHandler(oauthproxy))
} }
s := &Server{ s := &Server{
Handler: handler, Handler: handler,

View File

@ -34,6 +34,7 @@ type Options struct {
ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets" env:"OAUTH2_PROXY_PROXY_WEBSOCKETS"` ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets" env:"OAUTH2_PROXY_PROXY_WEBSOCKETS"`
HTTPAddress string `flag:"http-address" cfg:"http_address" env:"OAUTH2_PROXY_HTTP_ADDRESS"` HTTPAddress string `flag:"http-address" cfg:"http_address" env:"OAUTH2_PROXY_HTTP_ADDRESS"`
HTTPSAddress string `flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS"` HTTPSAddress string `flag:"https-address" cfg:"https_address" env:"OAUTH2_PROXY_HTTPS_ADDRESS"`
ForceHTTPS bool `flag:"force-https" cfg:"force_https" env:"OAUTH2_PROXY_FORCE_HTTPS"`
RedirectURL string `flag:"redirect-url" cfg:"redirect_url" env:"OAUTH2_PROXY_REDIRECT_URL"` RedirectURL string `flag:"redirect-url" cfg:"redirect_url" env:"OAUTH2_PROXY_REDIRECT_URL"`
ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"` ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"` ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
@ -145,6 +146,7 @@ func NewOptions() *Options {
ProxyWebSockets: true, ProxyWebSockets: true,
HTTPAddress: "127.0.0.1:4180", HTTPAddress: "127.0.0.1:4180",
HTTPSAddress: ":443", HTTPSAddress: ":443",
ForceHTTPS: false,
DisplayHtpasswdForm: true, DisplayHtpasswdForm: true,
CookieOptions: options.CookieOptions{ CookieOptions: options.CookieOptions{
CookieName: "_oauth2_proxy", CookieName: "_oauth2_proxy",

View File

@ -18,17 +18,23 @@ func Request(req *http.Request) (*simplejson.Json, error) {
return nil, err return nil, err
} }
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close() if body != nil {
logger.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body) defer resp.Body.Close()
if err != nil {
return nil, err
} }
logger.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body)
if err != nil {
return nil, fmt.Errorf("problem reading http request body: %w", err)
}
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return nil, fmt.Errorf("got %d %s", resp.StatusCode, body) return nil, fmt.Errorf("got %d %s", resp.StatusCode, body)
} }
data, err := simplejson.NewJson(body) data, err := simplejson.NewJson(body)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("error unmarshalling json: %w", err)
} }
return data, nil return data, nil
} }
@ -41,10 +47,13 @@ func RequestJSON(req *http.Request, v interface{}) error {
return err return err
} }
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close() if body != nil {
defer resp.Body.Close()
}
logger.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body) logger.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body)
if err != nil { if err != nil {
return err return fmt.Errorf("error reading body from http response: %w", err)
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return fmt.Errorf("got %d %s", resp.StatusCode, body) return fmt.Errorf("got %d %s", resp.StatusCode, body)
@ -56,7 +65,7 @@ func RequestJSON(req *http.Request, v interface{}) error {
func RequestUnparsedResponse(url string, header http.Header) (resp *http.Response, err error) { func RequestUnparsedResponse(url string, header http.Header) (resp *http.Response, err error) {
req, err := http.NewRequest("GET", url, nil) req, err := http.NewRequest("GET", url, nil)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("error performing get request: %w", err)
} }
req.Header = header req.Header = header

View File

@ -8,20 +8,21 @@ import (
"testing" "testing"
"github.com/bitly/go-simplejson" "github.com/bitly/go-simplejson"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func testBackend(responseCode int, payload string) *httptest.Server { func testBackend(t *testing.T, responseCode int, payload string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc( return httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(responseCode) w.WriteHeader(responseCode)
w.Write([]byte(payload)) _, err := w.Write([]byte(payload))
require.NoError(t, err)
})) }))
} }
func TestRequest(t *testing.T) { func TestRequest(t *testing.T) {
backend := testBackend(200, "{\"foo\": \"bar\"}") backend := testBackend(t, 200, "{\"foo\": \"bar\"}")
defer backend.Close() defer backend.Close()
req, _ := http.NewRequest("GET", backend.URL, nil) req, _ := http.NewRequest("GET", backend.URL, nil)
@ -35,7 +36,7 @@ func TestRequest(t *testing.T) {
func TestRequestFailure(t *testing.T) { func TestRequestFailure(t *testing.T) {
// Create a backend to generate a test URL, then close it to cause a // Create a backend to generate a test URL, then close it to cause a
// connection error. // connection error.
backend := testBackend(200, "{\"foo\": \"bar\"}") backend := testBackend(t, 200, "{\"foo\": \"bar\"}")
backend.Close() backend.Close()
req, err := http.NewRequest("GET", backend.URL, nil) req, err := http.NewRequest("GET", backend.URL, nil)
@ -49,7 +50,7 @@ func TestRequestFailure(t *testing.T) {
} }
func TestHttpErrorCode(t *testing.T) { func TestHttpErrorCode(t *testing.T) {
backend := testBackend(404, "{\"foo\": \"bar\"}") backend := testBackend(t, 404, "{\"foo\": \"bar\"}")
defer backend.Close() defer backend.Close()
req, err := http.NewRequest("GET", backend.URL, nil) req, err := http.NewRequest("GET", backend.URL, nil)
@ -60,7 +61,7 @@ func TestHttpErrorCode(t *testing.T) {
} }
func TestJsonParsingError(t *testing.T) { func TestJsonParsingError(t *testing.T) {
backend := testBackend(200, "not well-formed JSON") backend := testBackend(t, 200, "not well-formed JSON")
defer backend.Close() defer backend.Close()
req, err := http.NewRequest("GET", backend.URL, nil) req, err := http.NewRequest("GET", backend.URL, nil)
@ -77,7 +78,8 @@ func TestRequestUnparsedResponseUsingAccessTokenParameter(t *testing.T) {
token := r.FormValue("access_token") token := r.FormValue("access_token")
if r.URL.Path == "/" && token == "my_token" { if r.URL.Path == "/" && token == "my_token" {
w.WriteHeader(200) w.WriteHeader(200)
w.Write([]byte("some payload")) _, err := w.Write([]byte("some payload"))
require.NoError(t, err)
} else { } else {
w.WriteHeader(403) w.WriteHeader(403)
} }
@ -86,16 +88,17 @@ func TestRequestUnparsedResponseUsingAccessTokenParameter(t *testing.T) {
response, err := RequestUnparsedResponse( response, err := RequestUnparsedResponse(
backend.URL+"?access_token=my_token", nil) backend.URL+"?access_token=my_token", nil)
defer response.Body.Close()
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, 200, response.StatusCode) assert.Equal(t, 200, response.StatusCode)
body, err := ioutil.ReadAll(response.Body) body, err := ioutil.ReadAll(response.Body)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
response.Body.Close()
assert.Equal(t, "some payload", string(body)) assert.Equal(t, "some payload", string(body))
} }
func TestRequestUnparsedResponseUsingAccessTokenParameterFailedResponse(t *testing.T) { func TestRequestUnparsedResponseUsingAccessTokenParameterFailedResponse(t *testing.T) {
backend := testBackend(200, "some payload") backend := testBackend(t, 200, "some payload")
// Close the backend now to force a request failure. // Close the backend now to force a request failure.
backend.Close() backend.Close()
@ -110,7 +113,8 @@ func TestRequestUnparsedResponseUsingHeaders(t *testing.T) {
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" && r.Header["Auth"][0] == "my_token" { if r.URL.Path == "/" && r.Header["Auth"][0] == "my_token" {
w.WriteHeader(200) w.WriteHeader(200)
w.Write([]byte("some payload")) _, err := w.Write([]byte("some payload"))
require.NoError(t, err)
} else { } else {
w.WriteHeader(403) w.WriteHeader(403)
} }
@ -120,10 +124,12 @@ func TestRequestUnparsedResponseUsingHeaders(t *testing.T) {
headers := make(http.Header) headers := make(http.Header)
headers.Set("Auth", "my_token") headers.Set("Auth", "my_token")
response, err := RequestUnparsedResponse(backend.URL, headers) response, err := RequestUnparsedResponse(backend.URL, headers)
defer response.Body.Close()
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
assert.Equal(t, 200, response.StatusCode) assert.Equal(t, 200, response.StatusCode)
body, err := ioutil.ReadAll(response.Body) body, err := ioutil.ReadAll(response.Body)
assert.Equal(t, nil, err) assert.Equal(t, nil, err)
response.Body.Close()
assert.Equal(t, "some payload", string(body)) assert.Equal(t, "some payload", string(body))
} }

View File

@ -1,10 +1,14 @@
package providers package providers
import ( import (
"bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"time"
"github.com/bitly/go-simplejson" "github.com/bitly/go-simplejson"
"github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
@ -65,6 +69,67 @@ func (p *AzureProvider) Configure(tenant string) {
} }
} }
func (p *AzureProvider) Redeem(redirectURL, code string) (s *sessions.SessionState, err error) {
if code == "" {
err = errors.New("missing code")
return
}
params := url.Values{}
params.Add("redirect_uri", redirectURL)
params.Add("client_id", p.ClientID)
params.Add("client_secret", p.ClientSecret)
params.Add("code", code)
params.Add("grant_type", "authorization_code")
if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
params.Add("resource", p.ProtectedResource.String())
}
var req *http.Request
req, err = http.NewRequest("POST", p.RedeemURL.String(), bytes.NewBufferString(params.Encode()))
if err != nil {
return
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
var resp *http.Response
resp, err = http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
var body []byte
body, err = ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return
}
if resp.StatusCode != 200 {
err = fmt.Errorf("got %d from %q %s", resp.StatusCode, p.RedeemURL.String(), body)
return
}
var jsonResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresOn int64 `json:"expires_on,string"`
IDToken string `json:"id_token"`
}
err = json.Unmarshal(body, &jsonResponse)
if err != nil {
return
}
s = &sessions.SessionState{
AccessToken: jsonResponse.AccessToken,
IDToken: jsonResponse.IDToken,
CreatedAt: time.Now(),
ExpiresOn: time.Unix(jsonResponse.ExpiresOn, 0),
RefreshToken: jsonResponse.RefreshToken,
}
return
}
func getAzureHeader(accessToken string) http.Header { func getAzureHeader(accessToken string) http.Header {
header := make(http.Header) header := make(http.Header)
header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))

View File

@ -5,6 +5,7 @@ import (
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"testing" "testing"
"time"
"github.com/pusher/oauth2_proxy/pkg/apis/sessions" "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -20,6 +21,7 @@ func testAzureProvider(hostname string) *AzureProvider {
ValidateURL: &url.URL{}, ValidateURL: &url.URL{},
ProtectedResource: &url.URL{}, ProtectedResource: &url.URL{},
Scope: ""}) Scope: ""})
if hostname != "" { if hostname != "" {
updateURL(p.Data().LoginURL, hostname) updateURL(p.Data().LoginURL, hostname)
updateURL(p.Data().RedeemURL, hostname) updateURL(p.Data().RedeemURL, hostname)
@ -111,8 +113,11 @@ func testAzureBackend(payload string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc( return httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) { func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != path || r.URL.RawQuery != query { if (r.URL.Path != path || r.URL.RawQuery != query) && r.Method != "POST" {
w.WriteHeader(404) w.WriteHeader(404)
} else if r.Method == "POST" && r.Body != nil {
w.WriteHeader(200)
w.Write([]byte(payload))
} else if r.Header.Get("Authorization") != "Bearer imaginary_access_token" { } else if r.Header.Get("Authorization") != "Bearer imaginary_access_token" {
w.WriteHeader(403) w.WriteHeader(403)
} else { } else {
@ -199,3 +204,19 @@ func TestAzureProviderGetEmailAddressIncorrectOtherMails(t *testing.T) {
assert.Equal(t, "type assertion to string failed", err.Error()) assert.Equal(t, "type assertion to string failed", err.Error())
assert.Equal(t, "", email) assert.Equal(t, "", email)
} }
func TestAzureProviderRedeemReturnsIdToken(t *testing.T) {
b := testAzureBackend(`{ "id_token": "testtoken1234", "expires_on": "1136239445", "refresh_token": "refresh1234" }`)
defer b.Close()
timestamp, err := time.Parse(time.RFC3339, "2006-01-02T22:04:05Z")
assert.Equal(t, nil, err)
bURL, _ := url.Parse(b.URL)
p := testAzureProvider(bURL.Host)
p.Data().RedeemURL.Path = "/common/oauth2/token"
s, err := p.Redeem("https://localhost", "1234")
assert.Equal(t, nil, err)
assert.Equal(t, "testtoken1234", s.IDToken)
assert.Equal(t, timestamp, s.ExpiresOn.UTC())
assert.Equal(t, "refresh1234", s.RefreshToken)
}

View File

@ -8,30 +8,34 @@ import (
) )
type ValidatorTest struct { type ValidatorTest struct {
authEmailFile *os.File authEmailFileName string
done chan bool done chan bool
updateSeen bool updateSeen bool
} }
func NewValidatorTest(t *testing.T) *ValidatorTest { func NewValidatorTest(t *testing.T) *ValidatorTest {
vt := &ValidatorTest{} vt := &ValidatorTest{}
var err error var err error
vt.authEmailFile, err = ioutil.TempFile("", "test_auth_emails_") f, err := ioutil.TempFile("", "test_auth_emails_")
if err != nil { if err != nil {
t.Fatal("failed to create temp file: " + err.Error()) t.Fatalf("failed to create temp file: %v", err)
} }
if err := f.Close(); err != nil {
t.Fatalf("failed to close temp file: %v", err)
}
vt.authEmailFileName = f.Name()
vt.done = make(chan bool, 1) vt.done = make(chan bool, 1)
return vt return vt
} }
func (vt *ValidatorTest) TearDown() { func (vt *ValidatorTest) TearDown() {
vt.done <- true vt.done <- true
os.Remove(vt.authEmailFile.Name()) os.Remove(vt.authEmailFileName)
} }
func (vt *ValidatorTest) NewValidator(domains []string, func (vt *ValidatorTest) NewValidator(domains []string,
updated chan<- bool) func(string) bool { updated chan<- bool) func(string) bool {
return newValidatorImpl(domains, vt.authEmailFile.Name(), return newValidatorImpl(domains, vt.authEmailFileName,
vt.done, func() { vt.done, func() {
if vt.updateSeen == false { if vt.updateSeen == false {
updated <- true updated <- true
@ -40,13 +44,18 @@ func (vt *ValidatorTest) NewValidator(domains []string,
}) })
} }
// This will close vt.authEmailFile.
func (vt *ValidatorTest) WriteEmails(t *testing.T, emails []string) { func (vt *ValidatorTest) WriteEmails(t *testing.T, emails []string) {
defer vt.authEmailFile.Close() f, err := os.OpenFile(vt.authEmailFileName, os.O_WRONLY, 0600)
vt.authEmailFile.WriteString(strings.Join(emails, "\n")) if err != nil {
if err := vt.authEmailFile.Close(); err != nil { t.Fatalf("failed to open auth email file: %v", err)
t.Fatal("failed to close temp file " + }
vt.authEmailFile.Name() + ": " + err.Error())
if _, err := f.WriteString(strings.Join(emails, "\n")); err != nil {
t.Fatalf("failed to write emails to auth email file: %v", err)
}
if err := f.Close(); err != nil {
t.Fatalf("failed to close auth email file: %v", err)
} }
} }
@ -160,3 +169,43 @@ func TestValidatorIgnoreSpacesInAuthEmails(t *testing.T) {
t.Error("email should validate") t.Error("email should validate")
} }
} }
func TestValidatorOverwriteEmailListDirectly(t *testing.T) {
vt := NewValidatorTest(t)
defer vt.TearDown()
vt.WriteEmails(t, []string{
"xyzzy@example.com",
"plugh@example.com",
})
domains := []string(nil)
updated := make(chan bool)
validator := vt.NewValidator(domains, updated)
if !validator("xyzzy@example.com") {
t.Error("first email in list should validate")
}
if !validator("plugh@example.com") {
t.Error("second email in list should validate")
}
if validator("xyzzy.plugh@example.com") {
t.Error("email not in list that matches no domains " +
"should not validate")
}
vt.WriteEmails(t, []string{
"xyzzy.plugh@example.com",
"plugh@example.com",
})
<-updated
if validator("xyzzy@example.com") {
t.Error("email removed from list should not validate")
}
if !validator("plugh@example.com") {
t.Error("email retained in list should validate")
}
if !validator("xyzzy.plugh@example.com") {
t.Error("email added to list should validate")
}
}

View File

@ -1,48 +0,0 @@
// +build go1.3,!plan9,!solaris,!windows
// Turns out you can't copy over an existing file on Windows.
package main
import (
"io/ioutil"
"os"
"testing"
)
func (vt *ValidatorTest) UpdateEmailFileViaCopyingOver(
t *testing.T, emails []string) {
origFile := vt.authEmailFile
var err error
vt.authEmailFile, err = ioutil.TempFile("", "test_auth_emails_")
if err != nil {
t.Fatal("failed to create temp file for copy: " + err.Error())
}
vt.WriteEmails(t, emails)
err = os.Rename(vt.authEmailFile.Name(), origFile.Name())
if err != nil {
t.Fatal("failed to copy over temp file: " + err.Error())
}
vt.authEmailFile = origFile
}
func TestValidatorOverwriteEmailListViaCopyingOver(t *testing.T) {
vt := NewValidatorTest(t)
defer vt.TearDown()
vt.WriteEmails(t, []string{"xyzzy@example.com"})
domains := []string(nil)
updated := make(chan bool)
validator := vt.NewValidator(domains, updated)
if !validator("xyzzy@example.com") {
t.Error("email in list should validate")
}
vt.UpdateEmailFileViaCopyingOver(t, []string{"plugh@example.com"})
<-updated
if validator("xyzzy@example.com") {
t.Error("email removed from list should not validate")
}
}

View File

@ -1,102 +0,0 @@
// +build go1.3,!plan9,!solaris
package main
import (
"io/ioutil"
"os"
"testing"
)
func (vt *ValidatorTest) UpdateEmailFile(t *testing.T, emails []string) {
var err error
vt.authEmailFile, err = os.OpenFile(
vt.authEmailFile.Name(), os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
t.Fatal("failed to re-open temp file for updates")
}
vt.WriteEmails(t, emails)
}
func (vt *ValidatorTest) UpdateEmailFileViaRenameAndReplace(
t *testing.T, emails []string) {
origFile := vt.authEmailFile
var err error
vt.authEmailFile, err = ioutil.TempFile("", "test_auth_emails_")
if err != nil {
t.Fatal("failed to create temp file for rename and replace: " +
err.Error())
}
vt.WriteEmails(t, emails)
movedName := origFile.Name() + "-moved"
err = os.Rename(origFile.Name(), movedName)
err = os.Rename(vt.authEmailFile.Name(), origFile.Name())
if err != nil {
t.Fatal("failed to rename and replace temp file: " +
err.Error())
}
vt.authEmailFile = origFile
os.Remove(movedName)
}
func TestValidatorOverwriteEmailListDirectly(t *testing.T) {
vt := NewValidatorTest(t)
defer vt.TearDown()
vt.WriteEmails(t, []string{
"xyzzy@example.com",
"plugh@example.com",
})
domains := []string(nil)
updated := make(chan bool)
validator := vt.NewValidator(domains, updated)
if !validator("xyzzy@example.com") {
t.Error("first email in list should validate")
}
if !validator("plugh@example.com") {
t.Error("second email in list should validate")
}
if validator("xyzzy.plugh@example.com") {
t.Error("email not in list that matches no domains " +
"should not validate")
}
vt.UpdateEmailFile(t, []string{
"xyzzy.plugh@example.com",
"plugh@example.com",
})
<-updated
if validator("xyzzy@example.com") {
t.Error("email removed from list should not validate")
}
if !validator("plugh@example.com") {
t.Error("email retained in list should validate")
}
if !validator("xyzzy.plugh@example.com") {
t.Error("email added to list should validate")
}
}
func TestValidatorOverwriteEmailListViaRenameAndReplace(t *testing.T) {
vt := NewValidatorTest(t)
defer vt.TearDown()
vt.WriteEmails(t, []string{"xyzzy@example.com"})
domains := []string(nil)
updated := make(chan bool, 1)
validator := vt.NewValidator(domains, updated)
if !validator("xyzzy@example.com") {
t.Error("email in list should validate")
}
vt.UpdateEmailFileViaRenameAndReplace(t, []string{"plugh@example.com"})
<-updated
if validator("xyzzy@example.com") {
t.Error("email removed from list should not validate")
}
}