Merge pull request #179 from Ramblurr/nextcloud-provider
Add nextcloud provider
This commit is contained in:
		
						commit
						bb55b13242
					
				| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,7 @@ 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)
 | 
				
			||||||
 | 
					- [#179](https://github.com/pusher/oauth2_proxy/pull/179) Add Nextcloud provider (@Ramblurr)
 | 
				
			||||||
- [#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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -289,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=*`.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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