git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge branch 'master' into kamal/whitelist-redirects-with-ports
commit5489d1624ef3e798dd1de14da6ac22f26265a8fd authorKamal Nasser <hello@kamal.io> dateWed 8 Jan 2020, 22:24 (7 years ago) parent6d1b5fc7663565
14 files changed+409−63
.github/CODEOWNERS+4 CHANGELOG.md+32−5 README.md+1−1 RELEASE.md+47 dist.sh+2−2 docs/2_auth.md+28 docs/configuration/configuration.md+2−1 main.go+2−1 oauthproxy.go+71−48 oauthproxy_test.go+34−5 options.go+1 providers/nextcloud.go+45 providers/nextcloud_test.go+138 providers/providers.go+2
.github/CODEOWNERS+4
@@ -14,3 +14,7 @@ providers/logingov_test.go @timothy-spencer
1414# Bitbucket provider
1515providers/bitbucket.go @aledeganopix4d
1616providers/bitbucket_test.go @aledeganopix4d
17+
18+# Nextcloud provider
19+providers/nextcloud.go @Ramblurr
20+providers/nextcloud_test.go @Ramblurr
CHANGELOG.md+32−5
@@ -1,25 +1,52 @@
11# Vx.x.x (Pre-release)
22
3+## Important Notes
4+
5+## Breaking Changes
6+
7+## Changes since v4.1.0
8+- [#325](https://github.com/pusher/oauth2_proxy/pull/325) dist.sh: use sha256sum (@syscll)
9+- [#179](https://github.com/pusher/oauth2_proxy/pull/179) Add Nextcloud provider (@Ramblurr)
10+- [#280](https://github.com/pusher/oauth2_proxy/pull/280) whitelisted redirect domains: add support for whitelisting specific ports or allowing wildcard ports (@kamaln7)
11+
12+# v4.1.0
13+
14+## Release Highlights
15+- Added Keycloak provider
16+- Build on Go 1.13
17+- Upgrade Docker image to use Debian Buster
18+- Added support for FreeBSD builds
19+- Added new logo
20+- Added support for GitHub teams
21+
22+## Important Notes
23+N/A
24+
25+## Breaking Changes
26+N/A
27+
328## Changes since v4.0.0
429- [#292](https://github.com/pusher/oauth2_proxy/pull/292) Added bash >= 4.0 dependency to configure script (@jmfrank63)
530- [#227](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka)
631- [#259](https://github.com/pusher/oauth2_proxy/pull/259) Redirect to HTTPS (@jmickey)
732- [#273](https://github.com/pusher/oauth2_proxy/pull/273) Support Go 1.13 (@dio)
833- [#275](https://github.com/pusher/oauth2_proxy/pull/275) docker: build from debian buster (@syscll)
9−- [#258](https://github.com/pusher/oauth2_proxy/pull/258) Add IDToken for Azure provider
34+- [#258](https://github.com/pusher/oauth2_proxy/pull/258) Add IDToken for Azure provider (@leyshon)
1035 - 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)
1136- [#286](https://github.com/pusher/oauth2_proxy/pull/286) Requests.go updated with useful error messages (@biotom)
1237- [#274](https://github.com/pusher/oauth2_proxy/pull/274) Supports many github teams with api pagination support (@toshi-miura, @apratina)
1338- [#302](https://github.com/pusher/oauth2_proxy/pull/302) Rewrite dist script (@syscll)
1439- [#304](https://github.com/pusher/oauth2_proxy/pull/304) Add new Logo! :tada: (@JoelSpeed)
1540- [#300](https://github.com/pusher/oauth2_proxy/pull/300) Added userinfo endpoint (@kbabuadze)
16−- [#309](https://github.com/pusher/oauth2_proxy/pull/309) Added support for custom CA when connecting to Redis cache
17−- [#280](https://github.com/pusher/oauth2_proxy/pull/280) Add support for whitelisting specific ports or allowing wildcard ports in whitelisted redirect domains
41+- [#309](https://github.com/pusher/oauth2_proxy/pull/309) Added support for custom CA when connecting to Redis cache (@lleszczu)
42+- [#248](https://github.com/pusher/oauth2_proxy/pull/248) Fix issue with X-Auth-Request-Redirect header being ignored (@webnard)
43+- [#314](https://github.com/pusher/oauth2_proxy/pull/314) Add redirect capability to sign_out (@costelmoraru)
44+- [#265](https://github.com/pusher/oauth2_proxy/pull/265) Add upstream with static response (@cgroschupp)
45+- [#317](https://github.com/pusher/oauth2_proxy/pull/317) Add build for FreeBSD (@fnkr)
46+- [#296](https://github.com/pusher/oauth2_proxy/pull/296) Allow to override provider's name for sign-in page (@ffdybuster)
1847
1948# v4.0.0
2049
21−- [#248](https://github.com/pusher/oauth2_proxy/pull/248) Fix issue with X-Auth-Request-Redirect header being ignored
22−
2350## Release Highlights
2451- Documentation is now on a [microsite](https://pusher.github.io/oauth2_proxy/)
2552- Health check logging can now be disabled for quieter logs
README.md+1−1
@@ -47,4 +47,4 @@ If you would like to reach out to the maintainers, come talk to us in the `#oaut
4747
4848## Contributing
4949
50−Please see our [Contributing](CONTRIBUTING.md) guidelines.
50+Please see our [Contributing](CONTRIBUTING.md) guidelines. For releasing see our [release creation guide](RELEASE.md).
RELEASE.mdnew file+47
@@ -0,0 +1,47 @@
1+# Release
2+
3+Here's how OAuth2_Proxy releases are created.
4+
5+## Schedule
6+
7+Our aim is to release once a quarter, but bug fixes will be prioritised and might be released earlier.
8+
9+## The Process
10+
11+Note this uses `v4.1.0` as an example release number.
12+
13+1. Create a draft Github release
14+ * Use format `v4.1.0` for both the tag and title
15+2. Update [CHANGELOG.md](CHANGELOG.md)
16+ * Write the release highlights
17+ * Copy in headings ready for the next release
18+3. Create release commit
19+ ```
20+ git checkout -b release-v4.1.0
21+ ```
22+4. Create pull request getting other maintainers to review
23+5. Copy the release notes in to the draft Github release, adding a link to [CHANGELOG.md](CHANGELOG.md)
24+6. Update you local master branch
25+ ```
26+ git checkout master
27+ git pull
28+ ```
29+7. Create & push the tag
30+ ```
31+ git tag v4.1.0
32+ git push --tags
33+ ```
34+8. Make the release artefacts
35+ ```
36+ make release
37+ ```
38+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`).
39+10. Publish release in Github
40+11. Make and push docker images to Quay
41+ ```
42+ make docker-all
43+ make docker-push-all
44+ ```
45+ Note: Ensure the docker tags don't include `-dirty`. This means you have uncommitted changes.
46+
47+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)
dist.sh+2−2
@@ -14,7 +14,7 @@ if [[ ! "${GO_VERSION}" =~ ^go1.13.* ]]; then
1414 exit 1
1515fi
1616
17−ARCHS=(darwin-amd64 linux-amd64 linux-arm64 linux-armv6 windows-amd64)
17+ARCHS=(darwin-amd64 linux-amd64 linux-arm64 linux-armv6 freebsd-amd64 windows-amd64)
1818
1919mkdir -p release
2020
@@ -37,7 +37,7 @@ for ARCH in "${ARCHS[@]}"; do
3737 cd release
3838
3939 # Create sha256sum for architecture specific binary
40− shasum -a 256 ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}/${BINARY} > ${BINARY}-${VERSION}.${ARCH}-sha256sum.txt
40+ sha256sum ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}/${BINARY} > ${BINARY}-${VERSION}.${ARCH}-sha256sum.txt
4141
4242 # Create tar file for architecture specific binary
4343 tar -czvf ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}.tar.gz ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}
docs/2_auth.md+28
@@ -19,6 +19,7 @@ Valid providers are :
1919- [GitLab](#gitlab-auth-provider)
2020- [LinkedIn](#linkedin-auth-provider)
2121- [login.gov](#logingov-provider)
22+- [Nextcloud](#nextcloud-provider)
2223
2324The provider can be selected using the `provider` configuration value.
2425
@@ -156,6 +157,7 @@ OpenID Connect is a spec for OAUTH 2.0 + identity that is implemented by many ma
1561573. Login with the fixture use in the dex guide and run the oauth2_proxy with the following args:
157158
158159 -provider oidc
160+ -provider-display-name "My OIDC Provider"
159161 -client-id oauth2_proxy
160162 -client-secret proxy
161163 -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
288290 -email-domain example.com
289291```
290292
293+### Nextcloud Provider
294+
295+The Nextcloud provider allows you to authenticate against users in your
296+Nextcloud instance.
297+
298+When you are using the Nextcloud provider, you must specify the urls via
299+configuration, environment variable, or command line argument. Depending
300+on whether your Nextcloud instance is using pretty urls your urls may be of the
301+form `/index.php/apps/oauth2/*` or `/apps/oauth2/*`.
302+
303+Refer to the [OAuth2
304+documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/oauth2.html)
305+to setup the client id and client secret. Your "Redirection URI" will be
306+`https://internalapp.yourcompany.com/oauth2/callback`.
307+
308+```
309+ -provider nextcloud
310+ -client-id <from nextcloud admin>
311+ -client-secret <from nextcloud admin>
312+ -login-url="<your nextcloud url>/index.php/apps/oauth2/authorize"
313+ -redeem-url="<your nextcloud url>/index.php/apps/oauth2/api/v1/token"
314+ -validate-url="<your nextcloud url>/ocs/v2.php/cloud/user?format=json"
315+```
316+
317+Note: in *all* cases the validate-url will *not* have the `index.php`.
318+
291319## Email Authentication
292320
293321To 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=*`.
docs/configuration/configuration.md+2−1
@@ -76,6 +76,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example
7676| `-pass-user-headers` | bool | pass X-Forwarded-User and X-Forwarded-Email information to upstream | true |
7777| `-profile-url` | string | Profile access endpoint | |
7878| `-provider` | string | OAuth provider | google |
79+| `-provider-display-name` | string | Override the provider's name with the given string; used for the sign-in page | (depends on provider) |
7980| `-ping-path` | string | the ping endpoint that can be used for basic health checks | `"/ping"` |
8081| `-proxy-prefix` | string | the url root path that this proxy should be nested under (e.g. /`<oauth2>/sign_in`) | `"/oauth2"` |
8182| `-proxy-websockets` | bool | enables WebSocket proxying | true |
@@ -106,7 +107,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example
106107| `-standard-logging-format` | string | Template for standard log lines | see [Logging Configuration](#logging-configuration) |
107108| `-tls-cert-file` | string | path to certificate file | |
108109| `-tls-key-file` | string | path to private key file | |
109−| `-upstream` | string \| list | the http url(s) of the upstream endpoint or `file://` paths for static files. Routing is based on the path | |
110+| `-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 | |
110111| `-validate-url` | string | Access token validation endpoint | |
111112| `-version` | n/a | print version string | |
112113| `-whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (eg `.example.com`) | |
main.go+2−1
@@ -37,7 +37,7 @@ func main() {
3737 flagSet.String("tls-key-file", "", "path to private key file")
3838 flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
3939 flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
40− flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint or file:// paths for static files. Routing is based on the path")
40+ 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")
4141 flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
4242 flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
4343 flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
@@ -114,6 +114,7 @@ func main() {
114114 flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines")
115115
116116 flagSet.String("provider", "google", "OAuth provider")
117+ flagSet.String("provider-display-name", "", "Provider display name")
117118 flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
118119 flagSet.Bool("insecure-oidc-allow-unverified-email", false, "Don't fail if an email address in an id_token is not verified")
119120 flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints")
oauthproxy.go+71−48
@@ -13,6 +13,7 @@ import (
1313 "net/http/httputil"
1414 "net/url"
1515 "regexp"
16+ "strconv"
1617 "strings"
1718 "time"
1819
@@ -78,31 +79,32 @@ type OAuthProxy struct {
7879 AuthOnlyPath string
7980 UserInfoPath string
8081
81− redirectURL *url.URL // the url to receive requests at
82− whitelistDomains []string
83− provider providers.Provider
84− sessionStore sessionsapi.SessionStore
85− ProxyPrefix string
86− SignInMessage string
87− HtpasswdFile *HtpasswdFile
88− DisplayHtpasswdForm bool
89− serveMux http.Handler
90− SetXAuthRequest bool
91− PassBasicAuth bool
92− SkipProviderButton bool
93− PassUserHeaders bool
94− BasicAuthPassword string
95− PassAccessToken bool
96− SetAuthorization bool
97− PassAuthorization bool
98− skipAuthRegex []string
99− skipAuthPreflight bool
100− skipJwtBearerTokens bool
101− jwtBearerVerifiers []*oidc.IDTokenVerifier
102− compiledRegex []*regexp.Regexp
103− templates *template.Template
104− Banner string
105− Footer string
82+ redirectURL *url.URL // the url to receive requests at
83+ whitelistDomains []string
84+ provider providers.Provider
85+ providerNameOverride string
86+ sessionStore sessionsapi.SessionStore
87+ ProxyPrefix string
88+ SignInMessage string
89+ HtpasswdFile *HtpasswdFile
90+ DisplayHtpasswdForm bool
91+ serveMux http.Handler
92+ SetXAuthRequest bool
93+ PassBasicAuth bool
94+ SkipProviderButton bool
95+ PassUserHeaders bool
96+ BasicAuthPassword string
97+ PassAccessToken bool
98+ SetAuthorization bool
99+ PassAuthorization bool
100+ skipAuthRegex []string
101+ skipAuthPreflight bool
102+ skipJwtBearerTokens bool
103+ jwtBearerVerifiers []*oidc.IDTokenVerifier
104+ compiledRegex []*regexp.Regexp
105+ templates *template.Template
106+ Banner string
107+ Footer string
106108}
107109
108110// UpstreamProxy represents an upstream server to proxy to
@@ -203,12 +205,23 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
203205 }
204206 for _, u := range opts.proxyURLs {
205207 path := u.Path
208+ host := u.Host
206209 switch u.Scheme {
207210 case httpScheme, httpsScheme:
208211 logger.Printf("mapping path %q => upstream %q", path, u)
209212 proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
210213 serveMux.Handle(path, proxy)
214+ case "static":
215+ responseCode, err := strconv.Atoi(host)
216+ if err != nil {
217+ logger.Printf("unable to convert %q to int, use default \"200\"", host)
218+ responseCode = 200
219+ }
211220
221+ serveMux.HandleFunc(path, func(rw http.ResponseWriter, req *http.Request) {
222+ rw.WriteHeader(responseCode)
223+ fmt.Fprintf(rw, "Authenticated")
224+ })
212225 case "file":
213226 if u.Fragment != "" {
214227 path = u.Fragment
@@ -270,28 +283,29 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
270283 AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix),
271284 UserInfoPath: fmt.Sprintf("%s/userinfo", opts.ProxyPrefix),
272285
273− ProxyPrefix: opts.ProxyPrefix,
274− provider: opts.provider,
275− sessionStore: opts.sessionStore,
276− serveMux: serveMux,
277− redirectURL: redirectURL,
278− whitelistDomains: opts.WhitelistDomains,
279− skipAuthRegex: opts.SkipAuthRegex,
280− skipAuthPreflight: opts.SkipAuthPreflight,
281− skipJwtBearerTokens: opts.SkipJwtBearerTokens,
282− jwtBearerVerifiers: opts.jwtBearerVerifiers,
283− compiledRegex: opts.CompiledRegex,
284− SetXAuthRequest: opts.SetXAuthRequest,
285− PassBasicAuth: opts.PassBasicAuth,
286− PassUserHeaders: opts.PassUserHeaders,
287− BasicAuthPassword: opts.BasicAuthPassword,
288− PassAccessToken: opts.PassAccessToken,
289− SetAuthorization: opts.SetAuthorization,
290− PassAuthorization: opts.PassAuthorization,
291− SkipProviderButton: opts.SkipProviderButton,
292− templates: loadTemplates(opts.CustomTemplatesDir),
293− Banner: opts.Banner,
294− Footer: opts.Footer,
286+ ProxyPrefix: opts.ProxyPrefix,
287+ provider: opts.provider,
288+ providerNameOverride: opts.ProviderName,
289+ sessionStore: opts.sessionStore,
290+ serveMux: serveMux,
291+ redirectURL: redirectURL,
292+ whitelistDomains: opts.WhitelistDomains,
293+ skipAuthRegex: opts.SkipAuthRegex,
294+ skipAuthPreflight: opts.SkipAuthPreflight,
295+ skipJwtBearerTokens: opts.SkipJwtBearerTokens,
296+ jwtBearerVerifiers: opts.jwtBearerVerifiers,
297+ compiledRegex: opts.CompiledRegex,
298+ SetXAuthRequest: opts.SetXAuthRequest,
299+ PassBasicAuth: opts.PassBasicAuth,
300+ PassUserHeaders: opts.PassUserHeaders,
301+ BasicAuthPassword: opts.BasicAuthPassword,
302+ PassAccessToken: opts.PassAccessToken,
303+ SetAuthorization: opts.SetAuthorization,
304+ PassAuthorization: opts.PassAuthorization,
305+ SkipProviderButton: opts.SkipProviderButton,
306+ templates: loadTemplates(opts.CustomTemplatesDir),
307+ Banner: opts.Banner,
308+ Footer: opts.Footer,
295309 }
296310}
297311
@@ -453,6 +467,9 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
453467 ProxyPrefix: p.ProxyPrefix,
454468 Footer: template.HTML(p.Footer),
455469 }
470+ if p.providerNameOverride != "" {
471+ t.ProviderName = p.providerNameOverride
472+ }
456473 p.templates.ExecuteTemplate(rw, "sign_in.html", t)
457474}
458475
@@ -662,8 +679,14 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
662679
663680// SignOut sends a response to clear the authentication cookie
664681func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
682+ redirect, err := p.GetRedirect(req)
683+ if err != nil {
684+ logger.Printf("Error obtaining redirect: %s", err.Error())
685+ p.ErrorPage(rw, 500, "Internal Error", err.Error())
686+ return
687+ }
665688 p.ClearSessionCookie(rw, req)
666− http.Redirect(rw, req, "/", 302)
689+ http.Redirect(rw, req, redirect, 302)
667690}
668691
669692// OAuthStart starts the OAuth2 authentication flow
oauthproxy_test.go+34−5
@@ -474,6 +474,7 @@ type PassAccessTokenTest struct {
474474
475475type PassAccessTokenTestOptions struct {
476476 PassAccessToken bool
477+ ProxyUpstream string
477478}
478479
479480func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
@@ -481,7 +482,6 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
481482
482483 t.providerServer = httptest.NewServer(
483484 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
484− logger.Printf("%#v", r)
485485 var payload string
486486 switch r.URL.Path {
487487 case "/oauth/token":
@@ -498,6 +498,9 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
498498
499499 t.opts = NewOptions()
500500 t.opts.Upstreams = append(t.opts.Upstreams, t.providerServer.URL)
501+ if opts.ProxyUpstream != "" {
502+ t.opts.Upstreams = append(t.opts.Upstreams, opts.ProxyUpstream)
503+ }
501504 // The CookieSecret must be 32 bytes in order to create the AES
502505 // cipher.
503506 t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
@@ -534,7 +537,9 @@ func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
534537 return rw.Code, rw.HeaderMap["Set-Cookie"][1]
535538}
536539
537−func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int, accessToken string) {
540+// getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
541+// and cookie and returns body and status code.
542+func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
538543 cookieName := patTest.proxy.CookieName
539544 var value string
540545 keyPrefix := cookieName + "="
@@ -551,7 +556,7 @@ func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int
551556 return 0, ""
552557 }
553558
554− req, err := http.NewRequest("GET", "/", strings.NewReader(""))
559+ req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
555560 if err != nil {
556561 return 0, ""
557562 }
@@ -584,13 +589,37 @@ func TestForwardAccessTokenUpstream(t *testing.T) {
584589 // Now we make a regular request; the access_token from the cookie is
585590 // forwarded as the "X-Forwarded-Access-Token" header. The token is
586591 // read by the test provider server and written in the response body.
587− code, payload := patTest.getRootEndpoint(cookie)
592+ code, payload := patTest.getEndpointWithCookie(cookie, "/")
588593 if code != 200 {
589594 t.Fatalf("expected 200; got %d", code)
590595 }
591596 assert.Equal(t, "my_auth_token", payload)
592597}
593598
599+func TestStaticProxyUpstream(t *testing.T) {
600+ patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
601+ PassAccessToken: true,
602+ ProxyUpstream: "static://200/static-proxy",
603+ })
604+
605+ defer patTest.Close()
606+
607+ // A successful validation will redirect and set the auth cookie.
608+ code, cookie := patTest.getCallbackEndpoint()
609+ if code != 302 {
610+ t.Fatalf("expected 302; got %d", code)
611+ }
612+ assert.NotEqual(t, nil, cookie)
613+
614+ // Now we make a regular request againts the upstream proxy; And validate
615+ // the returned status code through the static proxy.
616+ code, payload := patTest.getEndpointWithCookie(cookie, "/static-proxy")
617+ if code != 200 {
618+ t.Fatalf("expected 200; got %d", code)
619+ }
620+ assert.Equal(t, "Authenticated", payload)
621+}
622+
594623func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
595624 patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
596625 PassAccessToken: false,
@@ -606,7 +635,7 @@ func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
606635
607636 // Now we make a regular request, but the access token header should
608637 // not be present.
609− code, payload := patTest.getRootEndpoint(cookie)
638+ code, payload := patTest.getEndpointWithCookie(cookie, "/")
610639 if code != 200 {
611640 t.Fatalf("expected 200; got %d", code)
612641 }
options.go+1
@@ -87,6 +87,7 @@ type Options struct {
8787 // These options allow for other providers besides Google, with
8888 // potential overrides.
8989 Provider string `flag:"provider" cfg:"provider" env:"OAUTH2_PROXY_PROVIDER"`
90+ ProviderName string `flag:"provider-display-name" cfg:"provider_display_name" env:"OAUTH2_PROXY_PROVIDER_DISPLAY_NAME"`
9091 OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url" env:"OAUTH2_PROXY_OIDC_ISSUER_URL"`
9192 InsecureOIDCAllowUnverifiedEmail bool `flag:"insecure-oidc-allow-unverified-email" cfg:"insecure_oidc_allow_unverified_email" env:"OAUTH2_PROXY_INSECURE_OIDC_ALLOW_UNVERIFIED_EMAIL"`
9293 SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery" env:"OAUTH2_PROXY_SKIP_OIDC_DISCOVERY"`
providers/nextcloud.gonew file+45
@@ -0,0 +1,45 @@
1+package providers
2+
3+import (
4+ "fmt"
5+ "net/http"
6+
7+ "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
8+ "github.com/pusher/oauth2_proxy/pkg/logger"
9+ "github.com/pusher/oauth2_proxy/pkg/requests"
10+)
11+
12+// NextcloudProvider represents an Nextcloud based Identity Provider
13+type NextcloudProvider struct {
14+ *ProviderData
15+}
16+
17+// NewNextcloudProvider initiates a new NextcloudProvider
18+func NewNextcloudProvider(p *ProviderData) *NextcloudProvider {
19+ p.ProviderName = "Nextcloud"
20+ return &NextcloudProvider{ProviderData: p}
21+}
22+
23+func getNextcloudHeader(accessToken string) http.Header {
24+ header := make(http.Header)
25+ header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
26+ return header
27+}
28+
29+// GetEmailAddress returns the Account email address
30+func (p *NextcloudProvider) GetEmailAddress(s *sessions.SessionState) (string, error) {
31+ req, err := http.NewRequest("GET",
32+ p.ValidateURL.String(), nil)
33+ if err != nil {
34+ logger.Printf("failed building request %s", err)
35+ return "", err
36+ }
37+ req.Header = getNextcloudHeader(s.AccessToken)
38+ json, err := requests.Request(req)
39+ if err != nil {
40+ logger.Printf("failed making request %s", err)
41+ return "", err
42+ }
43+ email, err := json.Get("ocs").Get("data").Get("email").String()
44+ return email, err
45+}
providers/nextcloud_test.gonew file+138
@@ -0,0 +1,138 @@
1+package providers
2+
3+import (
4+ "net/http"
5+ "net/http/httptest"
6+ "net/url"
7+ "testing"
8+
9+ "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
10+ "github.com/stretchr/testify/assert"
11+)
12+
13+const formatJSON = "format=json"
14+const userPath = "/ocs/v2.php/cloud/user"
15+
16+func testNextcloudProvider(hostname string) *NextcloudProvider {
17+ p := NewNextcloudProvider(
18+ &ProviderData{
19+ ProviderName: "",
20+ LoginURL: &url.URL{},
21+ RedeemURL: &url.URL{},
22+ ProfileURL: &url.URL{},
23+ ValidateURL: &url.URL{},
24+ Scope: ""})
25+ if hostname != "" {
26+ updateURL(p.Data().LoginURL, hostname)
27+ updateURL(p.Data().RedeemURL, hostname)
28+ updateURL(p.Data().ProfileURL, hostname)
29+ updateURL(p.Data().ValidateURL, hostname)
30+ }
31+ return p
32+}
33+
34+func testNextcloudBackend(payload string) *httptest.Server {
35+ path := userPath
36+ query := formatJSON
37+
38+ return httptest.NewServer(http.HandlerFunc(
39+ func(w http.ResponseWriter, r *http.Request) {
40+ if r.URL.Path != path || r.URL.RawQuery != query {
41+ w.WriteHeader(404)
42+ } else if r.Header.Get("Authorization") != "Bearer imaginary_access_token_nextcloud" {
43+ w.WriteHeader(403)
44+ } else {
45+ w.WriteHeader(200)
46+ w.Write([]byte(payload))
47+ }
48+ }))
49+}
50+
51+func TestNextcloudProviderDefaults(t *testing.T) {
52+ p := testNextcloudProvider("")
53+ assert.NotEqual(t, nil, p)
54+ assert.Equal(t, "Nextcloud", p.Data().ProviderName)
55+ assert.Equal(t, "",
56+ p.Data().LoginURL.String())
57+ assert.Equal(t, "",
58+ p.Data().RedeemURL.String())
59+ assert.Equal(t, "",
60+ p.Data().ValidateURL.String())
61+}
62+
63+func TestNextcloudProviderOverrides(t *testing.T) {
64+ p := NewNextcloudProvider(
65+ &ProviderData{
66+ LoginURL: &url.URL{
67+ Scheme: "https",
68+ Host: "example.com",
69+ Path: "/index.php/apps/oauth2/authorize"},
70+ RedeemURL: &url.URL{
71+ Scheme: "https",
72+ Host: "example.com",
73+ Path: "/index.php/apps/oauth2/api/v1/token"},
74+ ValidateURL: &url.URL{
75+ Scheme: "https",
76+ Host: "example.com",
77+ Path: "/test/ocs/v2.php/cloud/user",
78+ RawQuery: formatJSON},
79+ Scope: "profile"})
80+ assert.NotEqual(t, nil, p)
81+ assert.Equal(t, "Nextcloud", p.Data().ProviderName)
82+ assert.Equal(t, "https://example.com/index.php/apps/oauth2/authorize",
83+ p.Data().LoginURL.String())
84+ assert.Equal(t, "https://example.com/index.php/apps/oauth2/api/v1/token",
85+ p.Data().RedeemURL.String())
86+ assert.Equal(t, "https://example.com/test/ocs/v2.php/cloud/user?"+formatJSON,
87+ p.Data().ValidateURL.String())
88+}
89+
90+func TestNextcloudProviderGetEmailAddress(t *testing.T) {
91+ b := testNextcloudBackend("{\"ocs\": {\"data\": { \"email\": \"michael.bland@gsa.gov\"}}}")
92+ defer b.Close()
93+
94+ bURL, _ := url.Parse(b.URL)
95+ p := testNextcloudProvider(bURL.Host)
96+ p.ValidateURL.Path = userPath
97+ p.ValidateURL.RawQuery = formatJSON
98+
99+ session := &sessions.SessionState{AccessToken: "imaginary_access_token_nextcloud"}
100+ email, err := p.GetEmailAddress(session)
101+ assert.Equal(t, nil, err)
102+ assert.Equal(t, "michael.bland@gsa.gov", email)
103+}
104+
105+// Note that trying to trigger the "failed building request" case is not
106+// practical, since the only way it can fail is if the URL fails to parse.
107+func TestNextcloudProviderGetEmailAddressFailedRequest(t *testing.T) {
108+ b := testNextcloudBackend("unused payload")
109+ defer b.Close()
110+
111+ bURL, _ := url.Parse(b.URL)
112+ p := testNextcloudProvider(bURL.Host)
113+ p.ValidateURL.Path = userPath
114+ p.ValidateURL.RawQuery = formatJSON
115+
116+ // We'll trigger a request failure by using an unexpected access
117+ // token. Alternatively, we could allow the parsing of the payload as
118+ // JSON to fail.
119+ session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
120+ email, err := p.GetEmailAddress(session)
121+ assert.NotEqual(t, nil, err)
122+ assert.Equal(t, "", email)
123+}
124+
125+func TestNextcloudProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
126+ b := testNextcloudBackend("{\"foo\": \"bar\"}")
127+ defer b.Close()
128+
129+ bURL, _ := url.Parse(b.URL)
130+ p := testNextcloudProvider(bURL.Host)
131+ p.ValidateURL.Path = userPath
132+ p.ValidateURL.RawQuery = formatJSON
133+
134+ session := &sessions.SessionState{AccessToken: "imaginary_access_token_nextcloud"}
135+ email, err := p.GetEmailAddress(session)
136+ assert.NotEqual(t, nil, err)
137+ assert.Equal(t, "", email)
138+}
providers/providers.go+2
@@ -40,6 +40,8 @@ func New(provider string, p *ProviderData) Provider {
4040 return NewLoginGovProvider(p)
4141 case "bitbucket":
4242 return NewBitbucketProvider(p)
43+ case "nextcloud":
44+ return NewNextcloudProvider(p)
4345 default:
4446 return NewGoogleProvider(p)
4547 }