Merge branch 'master' into kamal/whitelist-redirects-with-ports
This commit is contained in:
		
						commit
						5489d1624e
					
				|  | @ -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). | ||||||
|  |  | ||||||
|  | @ -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") | ||||||
|  |  | ||||||
							
								
								
									
										119
									
								
								oauthproxy.go
								
								
								
								
							
							
						
						
									
										119
									
								
								oauthproxy.go
								
								
								
								
							|  | @ -13,6 +13,7 @@ import ( | ||||||
| 	"net/http/httputil" | 	"net/http/httputil" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | @ -78,31 +79,32 @@ type OAuthProxy struct { | ||||||
| 	AuthOnlyPath      string | 	AuthOnlyPath      string | ||||||
| 	UserInfoPath      string | 	UserInfoPath      string | ||||||
| 
 | 
 | ||||||
| 	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 | ||||||
| 	sessionStore        sessionsapi.SessionStore | 	providerNameOverride string | ||||||
| 	ProxyPrefix         string | 	sessionStore         sessionsapi.SessionStore | ||||||
| 	SignInMessage       string | 	ProxyPrefix          string | ||||||
| 	HtpasswdFile        *HtpasswdFile | 	SignInMessage        string | ||||||
| 	DisplayHtpasswdForm bool | 	HtpasswdFile         *HtpasswdFile | ||||||
| 	serveMux            http.Handler | 	DisplayHtpasswdForm  bool | ||||||
| 	SetXAuthRequest     bool | 	serveMux             http.Handler | ||||||
| 	PassBasicAuth       bool | 	SetXAuthRequest      bool | ||||||
| 	SkipProviderButton  bool | 	PassBasicAuth        bool | ||||||
| 	PassUserHeaders     bool | 	SkipProviderButton   bool | ||||||
| 	BasicAuthPassword   string | 	PassUserHeaders      bool | ||||||
| 	PassAccessToken     bool | 	BasicAuthPassword    string | ||||||
| 	SetAuthorization    bool | 	PassAccessToken      bool | ||||||
| 	PassAuthorization   bool | 	SetAuthorization     bool | ||||||
| 	skipAuthRegex       []string | 	PassAuthorization    bool | ||||||
| 	skipAuthPreflight   bool | 	skipAuthRegex        []string | ||||||
| 	skipJwtBearerTokens bool | 	skipAuthPreflight    bool | ||||||
| 	jwtBearerVerifiers  []*oidc.IDTokenVerifier | 	skipJwtBearerTokens  bool | ||||||
| 	compiledRegex       []*regexp.Regexp | 	jwtBearerVerifiers   []*oidc.IDTokenVerifier | ||||||
| 	templates           *template.Template | 	compiledRegex        []*regexp.Regexp | ||||||
| 	Banner              string | 	templates            *template.Template | ||||||
| 	Footer              string | 	Banner               string | ||||||
|  | 	Footer               string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // UpstreamProxy represents an upstream server to proxy to
 | // 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 { | 	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 | ||||||
|  | @ -270,28 +283,29 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy { | ||||||
| 		AuthOnlyPath:      fmt.Sprintf("%s/auth", opts.ProxyPrefix), | 		AuthOnlyPath:      fmt.Sprintf("%s/auth", opts.ProxyPrefix), | ||||||
| 		UserInfoPath:      fmt.Sprintf("%s/userinfo", opts.ProxyPrefix), | 		UserInfoPath:      fmt.Sprintf("%s/userinfo", opts.ProxyPrefix), | ||||||
| 
 | 
 | ||||||
| 		ProxyPrefix:         opts.ProxyPrefix, | 		ProxyPrefix:          opts.ProxyPrefix, | ||||||
| 		provider:            opts.provider, | 		provider:             opts.provider, | ||||||
| 		sessionStore:        opts.sessionStore, | 		providerNameOverride: opts.ProviderName, | ||||||
| 		serveMux:            serveMux, | 		sessionStore:         opts.sessionStore, | ||||||
| 		redirectURL:         redirectURL, | 		serveMux:             serveMux, | ||||||
| 		whitelistDomains:    opts.WhitelistDomains, | 		redirectURL:          redirectURL, | ||||||
| 		skipAuthRegex:       opts.SkipAuthRegex, | 		whitelistDomains:     opts.WhitelistDomains, | ||||||
| 		skipAuthPreflight:   opts.SkipAuthPreflight, | 		skipAuthRegex:        opts.SkipAuthRegex, | ||||||
| 		skipJwtBearerTokens: opts.SkipJwtBearerTokens, | 		skipAuthPreflight:    opts.SkipAuthPreflight, | ||||||
| 		jwtBearerVerifiers:  opts.jwtBearerVerifiers, | 		skipJwtBearerTokens:  opts.SkipJwtBearerTokens, | ||||||
| 		compiledRegex:       opts.CompiledRegex, | 		jwtBearerVerifiers:   opts.jwtBearerVerifiers, | ||||||
| 		SetXAuthRequest:     opts.SetXAuthRequest, | 		compiledRegex:        opts.CompiledRegex, | ||||||
| 		PassBasicAuth:       opts.PassBasicAuth, | 		SetXAuthRequest:      opts.SetXAuthRequest, | ||||||
| 		PassUserHeaders:     opts.PassUserHeaders, | 		PassBasicAuth:        opts.PassBasicAuth, | ||||||
| 		BasicAuthPassword:   opts.BasicAuthPassword, | 		PassUserHeaders:      opts.PassUserHeaders, | ||||||
| 		PassAccessToken:     opts.PassAccessToken, | 		BasicAuthPassword:    opts.BasicAuthPassword, | ||||||
| 		SetAuthorization:    opts.SetAuthorization, | 		PassAccessToken:      opts.PassAccessToken, | ||||||
| 		PassAuthorization:   opts.PassAuthorization, | 		SetAuthorization:     opts.SetAuthorization, | ||||||
| 		SkipProviderButton:  opts.SkipProviderButton, | 		PassAuthorization:    opts.PassAuthorization, | ||||||
| 		templates:           loadTemplates(opts.CustomTemplatesDir), | 		SkipProviderButton:   opts.SkipProviderButton, | ||||||
| 		Banner:              opts.Banner, | 		templates:            loadTemplates(opts.CustomTemplatesDir), | ||||||
| 		Footer:              opts.Footer, | 		Banner:               opts.Banner, | ||||||
|  | 		Footer:               opts.Footer, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -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"` | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | } | ||||||
|  | @ -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…
	
		Reference in New Issue