1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-02-13 13:59:53 +02:00

Merge branch 'master' into kamal/whitelist-redirects-with-ports

This commit is contained in:
Kamal Nasser 2020-01-08 22:24:56 +02:00 committed by GitHub
commit 5489d1624e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 409 additions and 63 deletions

4
.github/CODEOWNERS vendored
View File

@ -14,3 +14,7 @@ providers/logingov_test.go @timothy-spencer
# Bitbucket provider # Bitbucket provider
providers/bitbucket.go @aledeganopix4d providers/bitbucket.go @aledeganopix4d
providers/bitbucket_test.go @aledeganopix4d providers/bitbucket_test.go @aledeganopix4d
# Nextcloud provider
providers/nextcloud.go @Ramblurr
providers/nextcloud_test.go @Ramblurr

View File

@ -1,25 +1,52 @@
# Vx.x.x (Pre-release) # Vx.x.x (Pre-release)
## Important Notes
## Breaking Changes
## Changes since v4.1.0
- [#325](https://github.com/pusher/oauth2_proxy/pull/325) dist.sh: use sha256sum (@syscll)
- [#179](https://github.com/pusher/oauth2_proxy/pull/179) Add Nextcloud provider (@Ramblurr)
- [#280](https://github.com/pusher/oauth2_proxy/pull/280) whitelisted redirect domains: add support for whitelisting specific ports or allowing wildcard ports (@kamaln7)
# v4.1.0
## Release Highlights
- Added Keycloak provider
- Build on Go 1.13
- Upgrade Docker image to use Debian Buster
- Added support for FreeBSD builds
- Added new logo
- Added support for GitHub teams
## Important Notes
N/A
## Breaking Changes
N/A
## 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) - [#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) - [#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 - [#258](https://github.com/pusher/oauth2_proxy/pull/258) Add IDToken for Azure provider (@leyshon)
- 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) - 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) - [#286](https://github.com/pusher/oauth2_proxy/pull/286) Requests.go updated with useful error messages (@biotom)
- [#274](https://github.com/pusher/oauth2_proxy/pull/274) Supports many github teams with api pagination support (@toshi-miura, @apratina) - [#274](https://github.com/pusher/oauth2_proxy/pull/274) Supports many github teams with api pagination support (@toshi-miura, @apratina)
- [#302](https://github.com/pusher/oauth2_proxy/pull/302) Rewrite dist script (@syscll) - [#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) - [#304](https://github.com/pusher/oauth2_proxy/pull/304) Add new Logo! :tada: (@JoelSpeed)
- [#300](https://github.com/pusher/oauth2_proxy/pull/300) Added userinfo endpoint (@kbabuadze) - [#300](https://github.com/pusher/oauth2_proxy/pull/300) Added userinfo endpoint (@kbabuadze)
- [#309](https://github.com/pusher/oauth2_proxy/pull/309) Added support for custom CA when connecting to Redis cache - [#309](https://github.com/pusher/oauth2_proxy/pull/309) Added support for custom CA when connecting to Redis cache (@lleszczu)
- [#280](https://github.com/pusher/oauth2_proxy/pull/280) Add support for whitelisting specific ports or allowing wildcard ports in whitelisted redirect domains - [#248](https://github.com/pusher/oauth2_proxy/pull/248) Fix issue with X-Auth-Request-Redirect header being ignored (@webnard)
- [#314](https://github.com/pusher/oauth2_proxy/pull/314) Add redirect capability to sign_out (@costelmoraru)
- [#265](https://github.com/pusher/oauth2_proxy/pull/265) Add upstream with static response (@cgroschupp)
- [#317](https://github.com/pusher/oauth2_proxy/pull/317) Add build for FreeBSD (@fnkr)
- [#296](https://github.com/pusher/oauth2_proxy/pull/296) Allow to override provider's name for sign-in page (@ffdybuster)
# v4.0.0 # v4.0.0
- [#248](https://github.com/pusher/oauth2_proxy/pull/248) Fix issue with X-Auth-Request-Redirect header being ignored
## Release Highlights ## Release Highlights
- Documentation is now on a [microsite](https://pusher.github.io/oauth2_proxy/) - Documentation is now on a [microsite](https://pusher.github.io/oauth2_proxy/)
- Health check logging can now be disabled for quieter logs - Health check logging can now be disabled for quieter logs

View File

@ -47,4 +47,4 @@ If you would like to reach out to the maintainers, come talk to us in the `#oaut
## Contributing ## Contributing
Please see our [Contributing](CONTRIBUTING.md) guidelines. Please see our [Contributing](CONTRIBUTING.md) guidelines. For releasing see our [release creation guide](RELEASE.md).

47
RELEASE.md Normal file
View File

@ -0,0 +1,47 @@
# Release
Here's how OAuth2_Proxy releases are created.
## Schedule
Our aim is to release once a quarter, but bug fixes will be prioritised and might be released earlier.
## The Process
Note this uses `v4.1.0` as an example release number.
1. Create a draft Github release
* Use format `v4.1.0` for both the tag and title
2. Update [CHANGELOG.md](CHANGELOG.md)
* Write the release highlights
* Copy in headings ready for the next release
3. Create release commit
```
git checkout -b release-v4.1.0
```
4. Create pull request getting other maintainers to review
5. Copy the release notes in to the draft Github release, adding a link to [CHANGELOG.md](CHANGELOG.md)
6. Update you local master branch
```
git checkout master
git pull
```
7. Create & push the tag
```
git tag v4.1.0
git push --tags
```
8. Make the release artefacts
```
make release
```
9. Upload all the files (not the folders) from the `/release` folder to Github release as binary artefacts. There should be both the tarballs (`tar.gz`) and the checksum files (`sha256sum.txt`).
10. Publish release in Github
11. Make and push docker images to Quay
```
make docker-all
make docker-push-all
```
Note: Ensure the docker tags don't include `-dirty`. This means you have uncommitted changes.
12. Verify everything looks good at [quay](https://quay.io/repository/pusher/oauth2_proxy?tag=latest&tab=tags) and [github](https://github.com/pusher/oauth2_proxy/releases)

View File

@ -14,7 +14,7 @@ if [[ ! "${GO_VERSION}" =~ ^go1.13.* ]]; then
exit 1 exit 1
fi fi
ARCHS=(darwin-amd64 linux-amd64 linux-arm64 linux-armv6 windows-amd64) ARCHS=(darwin-amd64 linux-amd64 linux-arm64 linux-armv6 freebsd-amd64 windows-amd64)
mkdir -p release mkdir -p release
@ -37,7 +37,7 @@ for ARCH in "${ARCHS[@]}"; do
cd release cd release
# Create sha256sum for architecture specific binary # Create sha256sum for architecture specific binary
shasum -a 256 ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}/${BINARY} > ${BINARY}-${VERSION}.${ARCH}-sha256sum.txt sha256sum ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}/${BINARY} > ${BINARY}-${VERSION}.${ARCH}-sha256sum.txt
# Create tar file for architecture specific binary # Create tar file for architecture specific binary
tar -czvf ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}.tar.gz ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION} tar -czvf ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}.tar.gz ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}

View File

@ -19,6 +19,7 @@ Valid providers are :
- [GitLab](#gitlab-auth-provider) - [GitLab](#gitlab-auth-provider)
- [LinkedIn](#linkedin-auth-provider) - [LinkedIn](#linkedin-auth-provider)
- [login.gov](#logingov-provider) - [login.gov](#logingov-provider)
- [Nextcloud](#nextcloud-provider)
The provider can be selected using the `provider` configuration value. The provider can be selected using the `provider` configuration value.
@ -156,6 +157,7 @@ OpenID Connect is a spec for OAUTH 2.0 + identity that is implemented by many ma
3. Login with the fixture use in the dex guide and run the oauth2_proxy with the following args: 3. Login with the fixture use in the dex guide and run the oauth2_proxy with the following args:
-provider oidc -provider oidc
-provider-display-name "My OIDC Provider"
-client-id oauth2_proxy -client-id oauth2_proxy
-client-secret proxy -client-secret proxy
-redirect-url http://127.0.0.1:4180/oauth2/callback -redirect-url http://127.0.0.1:4180/oauth2/callback
@ -288,6 +290,32 @@ In this case, you can set the `-skip-oidc-discovery` option, and supply those re
-email-domain example.com -email-domain example.com
``` ```
### Nextcloud Provider
The Nextcloud provider allows you to authenticate against users in your
Nextcloud instance.
When you are using the Nextcloud provider, you must specify the urls via
configuration, environment variable, or command line argument. Depending
on whether your Nextcloud instance is using pretty urls your urls may be of the
form `/index.php/apps/oauth2/*` or `/apps/oauth2/*`.
Refer to the [OAuth2
documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/oauth2.html)
to setup the client id and client secret. Your "Redirection URI" will be
`https://internalapp.yourcompany.com/oauth2/callback`.
```
-provider nextcloud
-client-id <from nextcloud admin>
-client-secret <from nextcloud admin>
-login-url="<your nextcloud url>/index.php/apps/oauth2/authorize"
-redeem-url="<your nextcloud url>/index.php/apps/oauth2/api/v1/token"
-validate-url="<your nextcloud url>/ocs/v2.php/cloud/user?format=json"
```
Note: in *all* cases the validate-url will *not* have the `index.php`.
## Email Authentication ## Email Authentication
To authorize by email domain use `--email-domain=yourcompany.com`. To authorize individual email addresses use `--authenticated-emails-file=/path/to/file` with one email per line. To authorize all email addresses use `--email-domain=*`. To authorize by email domain use `--email-domain=yourcompany.com`. To authorize individual email addresses use `--authenticated-emails-file=/path/to/file` with one email per line. To authorize all email addresses use `--email-domain=*`.

View File

@ -76,6 +76,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example
| `-pass-user-headers` | bool | pass X-Forwarded-User and X-Forwarded-Email information to upstream | true | | `-pass-user-headers` | bool | pass X-Forwarded-User and X-Forwarded-Email information to upstream | true |
| `-profile-url` | string | Profile access endpoint | | | `-profile-url` | string | Profile access endpoint | |
| `-provider` | string | OAuth provider | google | | `-provider` | string | OAuth provider | google |
| `-provider-display-name` | string | Override the provider's name with the given string; used for the sign-in page | (depends on provider) |
| `-ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` | | `-ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` |
| `-proxy-prefix` | string | the url root path that this proxy should be nested under (e.g. /`<oauth2>/sign_in`) | `"/oauth2"` | | `-proxy-prefix` | string | the url root path that this proxy should be nested under (e.g. /`<oauth2>/sign_in`) | `"/oauth2"` |
| `-proxy-websockets` | bool | enables WebSocket proxying | true | | `-proxy-websockets` | bool | enables WebSocket proxying | true |
@ -106,7 +107,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example
| `-standard-logging-format` | string | Template for standard log lines | see [Logging Configuration](#logging-configuration) | | `-standard-logging-format` | string | Template for standard log lines | see [Logging Configuration](#logging-configuration) |
| `-tls-cert-file` | string | path to certificate file | | | `-tls-cert-file` | string | path to certificate file | |
| `-tls-key-file` | string | path to private key file | | | `-tls-key-file` | string | path to private key file | |
| `-upstream` | string \| list | the http url(s) of the upstream endpoint or `file://` paths for static files. Routing is based on the path | | | `-upstream` | string \| list | the http url(s) of the upstream endpoint, file:// paths for static files or `static://<status_code>` for static response. Routing is based on the path | |
| `-validate-url` | string | Access token validation endpoint | | | `-validate-url` | string | Access token validation endpoint | |
| `-version` | n/a | print version string | | | `-version` | n/a | print version string | |
| `-whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (eg `.example.com`) | | | `-whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (eg `.example.com`) | |

View File

@ -37,7 +37,7 @@ func main() {
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\"")
flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)") flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path") flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint, file:// paths for static files or static://<status_code> for static response. Routing is based on the path")
flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream") flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream") flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header") flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
@ -114,6 +114,7 @@ func main() {
flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines") flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines")
flagSet.String("provider", "google", "OAuth provider") flagSet.String("provider", "google", "OAuth provider")
flagSet.String("provider-display-name", "", "Provider display name")
flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)") flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified") flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints") flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints")

View File

@ -13,6 +13,7 @@ import (
"net/http/httputil" "net/http/httputil"
"net/url" "net/url"
"regexp" "regexp"
"strconv"
"strings" "strings"
"time" "time"
@ -81,6 +82,7 @@ type OAuthProxy struct {
redirectURL *url.URL // the url to receive requests at redirectURL *url.URL // the url to receive requests at
whitelistDomains []string whitelistDomains []string
provider providers.Provider provider providers.Provider
providerNameOverride string
sessionStore sessionsapi.SessionStore sessionStore sessionsapi.SessionStore
ProxyPrefix string ProxyPrefix string
SignInMessage string SignInMessage string
@ -203,12 +205,23 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
} }
for _, u := range opts.proxyURLs { for _, u := range opts.proxyURLs {
path := u.Path path := u.Path
host := u.Host
switch u.Scheme { switch u.Scheme {
case httpScheme, httpsScheme: case httpScheme, httpsScheme:
logger.Printf("mapping path %q => upstream %q", path, u) logger.Printf("mapping path %q => upstream %q", path, u)
proxy := NewWebSocketOrRestReverseProxy(u, opts, auth) proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
serveMux.Handle(path, proxy) serveMux.Handle(path, proxy)
case "static":
responseCode, err := strconv.Atoi(host)
if err != nil {
logger.Printf("unable to convert %q to int, use default \"200\"", host)
responseCode = 200
}
serveMux.HandleFunc(path, func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(responseCode)
fmt.Fprintf(rw, "Authenticated")
})
case "file": case "file":
if u.Fragment != "" { if u.Fragment != "" {
path = u.Fragment path = u.Fragment
@ -272,6 +285,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
ProxyPrefix: opts.ProxyPrefix, ProxyPrefix: opts.ProxyPrefix,
provider: opts.provider, provider: opts.provider,
providerNameOverride: opts.ProviderName,
sessionStore: opts.sessionStore, sessionStore: opts.sessionStore,
serveMux: serveMux, serveMux: serveMux,
redirectURL: redirectURL, redirectURL: redirectURL,
@ -453,6 +467,9 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
ProxyPrefix: p.ProxyPrefix, ProxyPrefix: p.ProxyPrefix,
Footer: template.HTML(p.Footer), Footer: template.HTML(p.Footer),
} }
if p.providerNameOverride != "" {
t.ProviderName = p.providerNameOverride
}
p.templates.ExecuteTemplate(rw, "sign_in.html", t) p.templates.ExecuteTemplate(rw, "sign_in.html", t)
} }
@ -662,8 +679,14 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
// SignOut sends a response to clear the authentication cookie // SignOut sends a response to clear the authentication cookie
func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) { func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
redirect, err := p.GetRedirect(req)
if err != nil {
logger.Printf("Error obtaining redirect: %s", err.Error())
p.ErrorPage(rw, 500, "Internal Error", err.Error())
return
}
p.ClearSessionCookie(rw, req) p.ClearSessionCookie(rw, req)
http.Redirect(rw, req, "/", 302) http.Redirect(rw, req, redirect, 302)
} }
// OAuthStart starts the OAuth2 authentication flow // OAuthStart starts the OAuth2 authentication flow

View File

@ -474,6 +474,7 @@ type PassAccessTokenTest struct {
type PassAccessTokenTestOptions struct { type PassAccessTokenTestOptions struct {
PassAccessToken bool PassAccessToken bool
ProxyUpstream string
} }
func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest { func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
@ -481,7 +482,6 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
t.providerServer = httptest.NewServer( t.providerServer = httptest.NewServer(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Printf("%#v", r)
var payload string var payload string
switch r.URL.Path { switch r.URL.Path {
case "/oauth/token": case "/oauth/token":
@ -498,6 +498,9 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
t.opts = NewOptions() t.opts = NewOptions()
t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL) t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
if opts.ProxyUpstream != "" {
t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
}
// The CookieSecret must be 32 bytes in order to create the AES // The CookieSecret must be 32 bytes in order to create the AES
// cipher. // cipher.
t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp" t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
@ -534,7 +537,9 @@ func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
return rw.Code, rw.HeaderMap["Set-Cookie"][1] return rw.Code, rw.HeaderMap["Set-Cookie"][1]
} }
func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int, accessToken string) { // getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
// and cookie and returns body and status code.
func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
cookieName := patTest.proxy.CookieName cookieName := patTest.proxy.CookieName
var value string var value string
keyPrefix := cookieName + "=" keyPrefix := cookieName + "="
@ -551,7 +556,7 @@ func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int
return 0, "" return 0, ""
} }
req, err := http.NewRequest("GET", "/", strings.NewReader("")) req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
if err != nil { if err != nil {
return 0, "" return 0, ""
} }
@ -584,13 +589,37 @@ func TestForwardAccessTokenUpstream(t *testing.T) {
// Now we make a regular request; the access_token from the cookie is // Now we make a regular request; the access_token from the cookie is
// forwarded as the "X-Forwarded-Access-Token" header. The token is // forwarded as the "X-Forwarded-Access-Token" header. The token is
// read by the test provider server and written in the response body. // read by the test provider server and written in the response body.
code, payload := patTest.getRootEndpoint(cookie) code, payload := patTest.getEndpointWithCookie(cookie, "/")
if code != 200 { if code != 200 {
t.Fatalf("expected 200; got %d", code) t.Fatalf("expected 200; got %d", code)
} }
assert.Equal(t, "my_auth_token", payload) assert.Equal(t, "my_auth_token", payload)
} }
func TestStaticProxyUpstream(t *testing.T) {
patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
PassAccessToken: true,
ProxyUpstream: "static://200/static-proxy",
})
defer patTest.Close()
// A successful validation will redirect and set the auth cookie.
code, cookie := patTest.getCallbackEndpoint()
if code != 302 {
t.Fatalf("expected 302; got %d", code)
}
assert.NotEqual(t, nil, cookie)
// Now we make a regular request againts the upstream proxy; And validate
// the returned status code through the static proxy.
code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
if code != 200 {
t.Fatalf("expected 200; got %d", code)
}
assert.Equal(t, "Authenticated", payload)
}
func TestDoNotForwardAccessTokenUpstream(t *testing.T) { func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{ patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
PassAccessToken: false, PassAccessToken: false,
@ -606,7 +635,7 @@ func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
// Now we make a regular request, but the access token header should // Now we make a regular request, but the access token header should
// not be present. // not be present.
code, payload := patTest.getRootEndpoint(cookie) code, payload := patTest.getEndpointWithCookie(cookie, "/")
if code != 200 { if code != 200 {
t.Fatalf("expected 200; got %d", code) t.Fatalf("expected 200; got %d", code)
} }

View File

@ -87,6 +87,7 @@ type Options struct {
// These options allow for other providers besides Google, with // These options allow for other providers besides Google, with
// potential overrides. // potential overrides.
Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"` Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"`
ProviderName string `flag:"provider-display-name" cfg:"provider_display_name" env:"OAUTH2_PROXY_PROVIDER_DISPLAY_NAME"`
OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"` OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"`
InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"` InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"`
SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY"` SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY"`

45
providers/nextcloud.go Normal file
View File

@ -0,0 +1,45 @@
package providers
import (
"fmt"
"net/http"
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
"github.com/pusher/oauth2_proxy/pkg/logger"
"github.com/pusher/oauth2_proxy/pkg/requests"
)
// NextcloudProvider represents an Nextcloud based Identity Provider
type NextcloudProvider struct {
*ProviderData
}
// NewNextcloudProvider initiates a new NextcloudProvider
func NewNextcloudProvider(p *ProviderData) *NextcloudProvider {
p.ProviderName = "Nextcloud"
return &NextcloudProvider{ProviderData: p}
}
func getNextcloudHeader(accessToken string) http.Header {
header := make(http.Header)
header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
return header
}
// GetEmailAddress returns the Account email address
func (p *NextcloudProvider) GetEmailAddress(s *sessions.SessionState) (string, error) {
req, err := http.NewRequest("GET",
p.ValidateURL.String(), nil)
if err != nil {
logger.Printf("failed building request %s", err)
return "", err
}
req.Header = getNextcloudHeader(s.AccessToken)
json, err := requests.Request(req)
if err != nil {
logger.Printf("failed making request %s", err)
return "", err
}
email, err := json.Get("ocs").Get("data").Get("email").String()
return email, err
}

138
providers/nextcloud_test.go Normal file
View File

@ -0,0 +1,138 @@
package providers
import (
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
"github.com/stretchr/testify/assert"
)
const formatJSON = "format=json"
const userPath = "/ocs/v2.php/cloud/user"
func testNextcloudProvider(hostname string) *NextcloudProvider {
p := NewNextcloudProvider(
&ProviderData{
ProviderName: "",
LoginURL: &url.URL{},
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
Scope: ""})
if hostname != "" {
updateURL(p.Data().LoginURL, hostname)
updateURL(p.Data().RedeemURL, hostname)
updateURL(p.Data().ProfileURL, hostname)
updateURL(p.Data().ValidateURL, hostname)
}
return p
}
func testNextcloudBackend(payload string) *httptest.Server {
path := userPath
query := formatJSON
return httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != path || r.URL.RawQuery != query {
w.WriteHeader(404)
} else if r.Header.Get("Authorization") != "Bearer imaginary_access_token_nextcloud" {
w.WriteHeader(403)
} else {
w.WriteHeader(200)
w.Write([]byte(payload))
}
}))
}
func TestNextcloudProviderDefaults(t *testing.T) {
p := testNextcloudProvider("")
assert.NotEqual(t, nil, p)
assert.Equal(t, "Nextcloud", p.Data().ProviderName)
assert.Equal(t, "",
p.Data().LoginURL.String())
assert.Equal(t, "",
p.Data().RedeemURL.String())
assert.Equal(t, "",
p.Data().ValidateURL.String())
}
func TestNextcloudProviderOverrides(t *testing.T) {
p := NewNextcloudProvider(
&ProviderData{
LoginURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/index.php/apps/oauth2/authorize"},
RedeemURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/index.php/apps/oauth2/api/v1/token"},
ValidateURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/test/ocs/v2.php/cloud/user",
RawQuery: formatJSON},
Scope: "profile"})
assert.NotEqual(t, nil, p)
assert.Equal(t, "Nextcloud", p.Data().ProviderName)
assert.Equal(t, "https://example.com/index.php/apps/oauth2/authorize",
p.Data().LoginURL.String())
assert.Equal(t, "https://example.com/index.php/apps/oauth2/api/v1/token",
p.Data().RedeemURL.String())
assert.Equal(t, "https://example.com/test/ocs/v2.php/cloud/user?"+formatJSON,
p.Data().ValidateURL.String())
}
func TestNextcloudProviderGetEmailAddress(t *testing.T) {
b := testNextcloudBackend("{\"ocs\": {\"data\": { \"email\": \"michael.bland@gsa.gov\"}}}")
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testNextcloudProvider(bURL.Host)
p.ValidateURL.Path = userPath
p.ValidateURL.RawQuery = formatJSON
session := &sessions.SessionState{AccessToken: "imaginary_access_token_nextcloud"}
email, err := p.GetEmailAddress(session)
assert.Equal(t, nil, err)
assert.Equal(t, "michael.bland@gsa.gov", email)
}
// Note that trying to trigger the "failed building request" case is not
// practical, since the only way it can fail is if the URL fails to parse.
func TestNextcloudProviderGetEmailAddressFailedRequest(t *testing.T) {
b := testNextcloudBackend("unused payload")
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testNextcloudProvider(bURL.Host)
p.ValidateURL.Path = userPath
p.ValidateURL.RawQuery = formatJSON
// We'll trigger a request failure by using an unexpected access
// token. Alternatively, we could allow the parsing of the payload as
// JSON to fail.
session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
email, err := p.GetEmailAddress(session)
assert.NotEqual(t, nil, err)
assert.Equal(t, "", email)
}
func TestNextcloudProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
b := testNextcloudBackend("{\"foo\": \"bar\"}")
defer b.Close()
bURL, _ := url.Parse(b.URL)
p := testNextcloudProvider(bURL.Host)
p.ValidateURL.Path = userPath
p.ValidateURL.RawQuery = formatJSON
session := &sessions.SessionState{AccessToken: "imaginary_access_token_nextcloud"}
email, err := p.GetEmailAddress(session)
assert.NotEqual(t, nil, err)
assert.Equal(t, "", email)
}

View File

@ -40,6 +40,8 @@ func New(provider string, p *ProviderData) Provider {
return NewLoginGovProvider(p) return NewLoginGovProvider(p)
case "bitbucket": case "bitbucket":
return NewBitbucketProvider(p) return NewBitbucketProvider(p)
case "nextcloud":
return NewNextcloudProvider(p)
default: default:
return NewGoogleProvider(p) return NewGoogleProvider(p)
} }