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:
commit
5489d1624e
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@ -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
|
||||||
|
37
CHANGELOG.md
37
CHANGELOG.md
@ -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
|
||||||
|
@ -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
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
|
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}
|
||||||
|
@ -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=*`.
|
||||||
|
@ -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`) | |
|
||||||
|
3
main.go
3
main.go
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
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)
|
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)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user