Skip to content

Commit

Permalink
Merge pull request oauth2-proxy#179 from Ramblurr/nextcloud-provider
Browse files Browse the repository at this point in the history
Add nextcloud provider
  • Loading branch information
JoelSpeed authored Dec 17, 2019
2 parents 1fb6fb8 + 3a8b33a commit bb55b13
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ 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)
- [#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)
- [#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)
Expand Down
27 changes: 27 additions & 0 deletions docs/2_auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -289,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=*`.
Expand Down
45 changes: 45 additions & 0 deletions providers/nextcloud.go
Original file line number Diff line number Diff line change
@@ -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 changes: 138 additions & 0 deletions providers/nextcloud_test.go
Original file line number Diff line number Diff line change
@@ -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\": \"[email protected]\"}}}")
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, "[email protected]", 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)
}
2 changes: 2 additions & 0 deletions providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down

0 comments on commit bb55b13

Please sign in to comment.