Skip to content

Commit

Permalink
Add Nextcloud provider (oauth2-proxy#179)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ramblurr committed Sep 5, 2019
1 parent 82a3d5a commit 2766b07
Show file tree
Hide file tree
Showing 5 changed files with 215 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Changes since v4.0.0

- [#179](https://github.com/pusher/oauth2_proxy/pull/179) Add Nextcloud provider

# v4.0.0

## Release Highlights
Expand Down
28 changes: 28 additions & 0 deletions docs/2_auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,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 @@ -271,6 +272,33 @@ 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 gitlab url>/index.php/apps/oauth2/authorize"
-redeem-url="<your gitlab url>/index.php/apps/oauth2/api/v1/token"
-validate-url="<your gitlab 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
46 changes: 46 additions & 0 deletions providers/nextcloud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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 := api.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
}
135 changes: 135 additions & 0 deletions providers/nextcloud_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package providers

import (
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/pusher/oauth2_proxy/pkg/apis/sessions"
"github.com/stretchr/testify/assert"
)

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 := "/ocs/v2.php/cloud/user"
query := "format=json"

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" {
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: "format=json"},
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?format=json",
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 = "/ocs/v2.php/cloud/user"
p.ValidateURL.RawQuery = "format=json"

session := &sessions.SessionState{AccessToken: "imaginary_access_token"}
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 = "/ocs/v2.php/cloud/user"
p.ValidateURL.RawQuery = "format=json"

// 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 = "/ocs/v2.php/cloud/user"
p.ValidateURL.RawQuery = "format=json"

session := &sessions.SessionState{AccessToken: "imaginary_access_token"}
email, err := p.GetEmailAddress(session)
assert.NotEqual(t, nil, err)
assert.Equal(t, "", email)
}

0 comments on commit 2766b07

Please sign in to comment.