Merge branch 'master' into kamal/whitelist-redirects-with-ports
This commit is contained in:
commit
5489d1624e
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@ -14,3 +14,7 @@ providers/logingov_test.go @timothy-spencer
|
||||
# Bitbucket provider
|
||||
providers/bitbucket.go @aledeganopix4d
|
||||
providers/bitbucket_test.go @aledeganopix4d
|
||||
|
||||
# Nextcloud provider
|
||||
providers/nextcloud.go @Ramblurr
|
||||
providers/nextcloud_test.go @Ramblurr
|
||||
|
37
CHANGELOG.md
37
CHANGELOG.md
@ -1,25 +1,52 @@
|
||||
# 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
|
||||
- [#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)
|
||||
- [#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)
|
||||
- [#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)
|
||||
- [#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)
|
||||
- [#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)
|
||||
- [#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
|
||||
- [#280](https://github.com/pusher/oauth2_proxy/pull/280) Add support for whitelisting specific ports or allowing wildcard ports in whitelisted redirect domains
|
||||
- [#309](https://github.com/pusher/oauth2_proxy/pull/309) Added support for custom CA when connecting to Redis cache (@lleszczu)
|
||||
- [#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
|
||||
|
||||
- [#248](https://github.com/pusher/oauth2_proxy/pull/248) Fix issue with X-Auth-Request-Redirect header being ignored
|
||||
|
||||
## Release Highlights
|
||||
- Documentation is now on a [microsite](https://pusher.github.io/oauth2_proxy/)
|
||||
- Health check logging can now be disabled for quieter logs
|
||||
|
@ -47,4 +47,4 @@ If you would like to reach out to the maintainers, come talk to us in the `#oaut
|
||||
|
||||
## 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
47
RELEASE.md
Normal 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)
|
4
dist.sh
4
dist.sh
@ -14,7 +14,7 @@ if [[ ! "${GO_VERSION}" =~ ^go1.13.* ]]; then
|
||||
exit 1
|
||||
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
|
||||
|
||||
@ -37,7 +37,7 @@ for ARCH in "${ARCHS[@]}"; do
|
||||
cd release
|
||||
|
||||
# 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
|
||||
tar -czvf ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}.tar.gz ${BINARY}-${VERSION}.${ARCH}.${GO_VERSION}
|
||||
|
@ -19,6 +19,7 @@ Valid providers are :
|
||||
- [GitLab](#gitlab-auth-provider)
|
||||
- [LinkedIn](#linkedin-auth-provider)
|
||||
- [login.gov](#logingov-provider)
|
||||
- [Nextcloud](#nextcloud-provider)
|
||||
|
||||
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:
|
||||
|
||||
-provider oidc
|
||||
-provider-display-name "My OIDC Provider"
|
||||
-client-id oauth2_proxy
|
||||
-client-secret proxy
|
||||
-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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
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=*`.
|
||||
|
@ -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 |
|
||||
| `-profile-url` | string | Profile access endpoint | |
|
||||
| `-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"` |
|
||||
| `-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 |
|
||||
@ -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) |
|
||||
| `-tls-cert-file` | string | path to certificate 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 | |
|
||||
| `-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`) | |
|
||||
|
3
main.go
3
main.go
@ -37,7 +37,7 @@ func main() {
|
||||
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.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-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")
|
||||
@ -114,6 +114,7 @@ func main() {
|
||||
flagSet.String("auth-logging-format", logger.DefaultAuthLoggingFormat, "Template for authentication log lines")
|
||||
|
||||
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.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")
|
||||
|
119
oauthproxy.go
119
oauthproxy.go
@ -13,6 +13,7 @@ import (
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -78,31 +79,32 @@ type OAuthProxy struct {
|
||||
AuthOnlyPath string
|
||||
UserInfoPath string
|
||||
|
||||
redirectURL *url.URL // the url to receive requests at
|
||||
whitelistDomains []string
|
||||
provider providers.Provider
|
||||
sessionStore sessionsapi.SessionStore
|
||||
ProxyPrefix string
|
||||
SignInMessage string
|
||||
HtpasswdFile *HtpasswdFile
|
||||
DisplayHtpasswdForm bool
|
||||
serveMux http.Handler
|
||||
SetXAuthRequest bool
|
||||
PassBasicAuth bool
|
||||
SkipProviderButton bool
|
||||
PassUserHeaders bool
|
||||
BasicAuthPassword string
|
||||
PassAccessToken bool
|
||||
SetAuthorization bool
|
||||
PassAuthorization bool
|
||||
skipAuthRegex []string
|
||||
skipAuthPreflight bool
|
||||
skipJwtBearerTokens bool
|
||||
jwtBearerVerifiers []*oidc.IDTokenVerifier
|
||||
compiledRegex []*regexp.Regexp
|
||||
templates *template.Template
|
||||
Banner string
|
||||
Footer string
|
||||
redirectURL *url.URL // the url to receive requests at
|
||||
whitelistDomains []string
|
||||
provider providers.Provider
|
||||
providerNameOverride string
|
||||
sessionStore sessionsapi.SessionStore
|
||||
ProxyPrefix string
|
||||
SignInMessage string
|
||||
HtpasswdFile *HtpasswdFile
|
||||
DisplayHtpasswdForm bool
|
||||
serveMux http.Handler
|
||||
SetXAuthRequest bool
|
||||
PassBasicAuth bool
|
||||
SkipProviderButton bool
|
||||
PassUserHeaders bool
|
||||
BasicAuthPassword string
|
||||
PassAccessToken bool
|
||||
SetAuthorization bool
|
||||
PassAuthorization bool
|
||||
skipAuthRegex []string
|
||||
skipAuthPreflight bool
|
||||
skipJwtBearerTokens bool
|
||||
jwtBearerVerifiers []*oidc.IDTokenVerifier
|
||||
compiledRegex []*regexp.Regexp
|
||||
templates *template.Template
|
||||
Banner string
|
||||
Footer string
|
||||
}
|
||||
|
||||
// UpstreamProxy represents an upstream server to proxy to
|
||||
@ -203,12 +205,23 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
||||
}
|
||||
for _, u := range opts.proxyURLs {
|
||||
path := u.Path
|
||||
host := u.Host
|
||||
switch u.Scheme {
|
||||
case httpScheme, httpsScheme:
|
||||
logger.Printf("mapping path %q => upstream %q", path, u)
|
||||
proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
|
||||
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":
|
||||
if u.Fragment != "" {
|
||||
path = u.Fragment
|
||||
@ -270,28 +283,29 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
||||
AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix),
|
||||
UserInfoPath: fmt.Sprintf("%s/userinfo", opts.ProxyPrefix),
|
||||
|
||||
ProxyPrefix: opts.ProxyPrefix,
|
||||
provider: opts.provider,
|
||||
sessionStore: opts.sessionStore,
|
||||
serveMux: serveMux,
|
||||
redirectURL: redirectURL,
|
||||
whitelistDomains: opts.WhitelistDomains,
|
||||
skipAuthRegex: opts.SkipAuthRegex,
|
||||
skipAuthPreflight: opts.SkipAuthPreflight,
|
||||
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
|
||||
jwtBearerVerifiers: opts.jwtBearerVerifiers,
|
||||
compiledRegex: opts.CompiledRegex,
|
||||
SetXAuthRequest: opts.SetXAuthRequest,
|
||||
PassBasicAuth: opts.PassBasicAuth,
|
||||
PassUserHeaders: opts.PassUserHeaders,
|
||||
BasicAuthPassword: opts.BasicAuthPassword,
|
||||
PassAccessToken: opts.PassAccessToken,
|
||||
SetAuthorization: opts.SetAuthorization,
|
||||
PassAuthorization: opts.PassAuthorization,
|
||||
SkipProviderButton: opts.SkipProviderButton,
|
||||
templates: loadTemplates(opts.CustomTemplatesDir),
|
||||
Banner: opts.Banner,
|
||||
Footer: opts.Footer,
|
||||
ProxyPrefix: opts.ProxyPrefix,
|
||||
provider: opts.provider,
|
||||
providerNameOverride: opts.ProviderName,
|
||||
sessionStore: opts.sessionStore,
|
||||
serveMux: serveMux,
|
||||
redirectURL: redirectURL,
|
||||
whitelistDomains: opts.WhitelistDomains,
|
||||
skipAuthRegex: opts.SkipAuthRegex,
|
||||
skipAuthPreflight: opts.SkipAuthPreflight,
|
||||
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
|
||||
jwtBearerVerifiers: opts.jwtBearerVerifiers,
|
||||
compiledRegex: opts.CompiledRegex,
|
||||
SetXAuthRequest: opts.SetXAuthRequest,
|
||||
PassBasicAuth: opts.PassBasicAuth,
|
||||
PassUserHeaders: opts.PassUserHeaders,
|
||||
BasicAuthPassword: opts.BasicAuthPassword,
|
||||
PassAccessToken: opts.PassAccessToken,
|
||||
SetAuthorization: opts.SetAuthorization,
|
||||
PassAuthorization: opts.PassAuthorization,
|
||||
SkipProviderButton: opts.SkipProviderButton,
|
||||
templates: loadTemplates(opts.CustomTemplatesDir),
|
||||
Banner: opts.Banner,
|
||||
Footer: opts.Footer,
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,6 +467,9 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
|
||||
ProxyPrefix: p.ProxyPrefix,
|
||||
Footer: template.HTML(p.Footer),
|
||||
}
|
||||
if p.providerNameOverride != "" {
|
||||
t.ProviderName = p.providerNameOverride
|
||||
}
|
||||
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
|
||||
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)
|
||||
http.Redirect(rw, req, "/", 302)
|
||||
http.Redirect(rw, req, redirect, 302)
|
||||
}
|
||||
|
||||
// OAuthStart starts the OAuth2 authentication flow
|
||||
|
@ -474,6 +474,7 @@ type PassAccessTokenTest struct {
|
||||
|
||||
type PassAccessTokenTestOptions struct {
|
||||
PassAccessToken bool
|
||||
ProxyUpstream string
|
||||
}
|
||||
|
||||
func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTest {
|
||||
@ -481,7 +482,6 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
|
||||
|
||||
t.providerServer = httptest.NewServer(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Printf("%#v", r)
|
||||
var payload string
|
||||
switch r.URL.Path {
|
||||
case "/oauth/token":
|
||||
@ -498,6 +498,9 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) *PassAccessTokenTes
|
||||
|
||||
t.opts = NewOptions()
|
||||
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
|
||||
// cipher.
|
||||
t.opts.CookieSecret = "xyzzyplughxyzzyplughxyzzyplughxp"
|
||||
@ -534,7 +537,9 @@ func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int,
|
||||
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
|
||||
var value string
|
||||
keyPrefix := cookieName + "="
|
||||
@ -551,7 +556,7 @@ func (patTest *PassAccessTokenTest) getRootEndpoint(cookie string) (httpCode int
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "/", strings.NewReader(""))
|
||||
req, err := http.NewRequest("GET", endpoint, strings.NewReader(""))
|
||||
if err != nil {
|
||||
return 0, ""
|
||||
}
|
||||
@ -584,13 +589,37 @@ func TestForwardAccessTokenUpstream(t *testing.T) {
|
||||
// Now we make a regular request; the access_token from the cookie is
|
||||
// forwarded as the "X-Forwarded-Access-Token" header. The token is
|
||||
// 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 {
|
||||
t.Fatalf("expected 200; got %d", code)
|
||||
}
|
||||
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) {
|
||||
patTest := NewPassAccessTokenTest(PassAccessTokenTestOptions{
|
||||
PassAccessToken: false,
|
||||
@ -606,7 +635,7 @@ func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
|
||||
|
||||
// Now we make a regular request, but the access token header should
|
||||
// not be present.
|
||||
code, payload := patTest.getRootEndpoint(cookie)
|
||||
code, payload := patTest.getEndpointWithCookie(cookie, "/")
|
||||
if code != 200 {
|
||||
t.Fatalf("expected 200; got %d", code)
|
||||
}
|
||||
|
@ -87,6 +87,7 @@ type Options struct {
|
||||
// These options allow for other providers besides Google, with
|
||||
// potential overrides.
|
||||
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"`
|
||||
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"`
|
||||
|
45
providers/nextcloud.go
Normal file
45
providers/nextcloud.go
Normal 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
138
providers/nextcloud_test.go
Normal 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)
|
||||
}
|
@ -40,6 +40,8 @@ func New(provider string, p *ProviderData) Provider {
|
||||
return NewLoginGovProvider(p)
|
||||
case "bitbucket":
|
||||
return NewBitbucketProvider(p)
|
||||
case "nextcloud":
|
||||
return NewNextcloudProvider(p)
|
||||
default:
|
||||
return NewGoogleProvider(p)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user