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

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

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

4
.github/CODEOWNERS vendored
View File

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

View File

@ -1,25 +1,52 @@
# Vx.x.x (Pre-release)
## 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

View File

@ -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
View File

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

View File

@ -14,7 +14,7 @@ if [[ ! "${GO_VERSION}" =~ ^go1.13.* ]]; then
exit 1
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}

View File

@ -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=*`.

View File

@ -76,6 +76,7 @@ An example [oauth2_proxy.cfg]({{ site.gitweb }}/contrib/oauth2_proxy.cfg.example
| `-pass-user-headers` | bool | pass X-Forwarded-User and X-Forwarded-Email information to upstream | true |
| `-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`) | |

View File

@ -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")

View File

@ -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

View File

@ -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)
}

View File

@ -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
View File

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

138
providers/nextcloud_test.go Normal file
View File

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

View File

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