git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add Nextcloud provider (#179)
commit227ea5da44859c00cae1cee38541608e08da51f3 authorCasey Link <me@caseylink.com> dateWed 5 Jun 2019, 18:59 (7 years ago) parent11205c7
6 files changed+217
.github/CODEOWNERS+4 CHANGELOG.md+1 docs/2_auth.md+27 providers/nextcloud.go+45 providers/nextcloud_test.go+138 providers/providers.go+2
.github/CODEOWNERS+4
@@ -14,3 +14,7 @@ providers/logingov_test.go @timothy-spencer
1414# Bitbucket provider
1515providers/bitbucket.go @aledeganopix4d
1616providers/bitbucket_test.go @aledeganopix4d
17+
18+# Nextcloud provider
19+providers/nextcloud.go @Ramblurr
20+providers/nextcloud_test.go @Ramblurr
CHANGELOG.md+1
@@ -3,6 +3,7 @@
33## Changes since v4.0.0
44- [#292](https://github.com/pusher/oauth2_proxy/pull/292) Added bash >= 4.0 dependency to configure script (@jmfrank63)
55- [#227](https://github.com/pusher/oauth2_proxy/pull/227) Add Keycloak provider (@Ofinka)
6+- [#179](https://github.com/pusher/oauth2_proxy/pull/179) Add Nextcloud provider (@Ramblurr)
67- [#259](https://github.com/pusher/oauth2_proxy/pull/259) Redirect to HTTPS (@jmickey)
78- [#273](https://github.com/pusher/oauth2_proxy/pull/273) Support Go 1.13 (@dio)
89- [#275](https://github.com/pusher/oauth2_proxy/pull/275) docker: build from debian buster (@syscll)
docs/2_auth.md+27
@@ -19,6 +19,7 @@ Valid providers are :
1919- [GitLab](#gitlab-auth-provider)
2020- [LinkedIn](#linkedin-auth-provider)
2121- [login.gov](#logingov-provider)
22+- [Nextcloud](#nextcloud-provider)
2223
2324The provider can be selected using the `provider` configuration value.
2425
@@ -289,6 +290,32 @@ In this case, you can set the `-skip-oidc-discovery` option, and supply those re
289290 -email-domain example.com
290291```
291292
293+### Nextcloud Provider
294+
295+The Nextcloud provider allows you to authenticate against users in your
296+Nextcloud instance.
297+
298+When you are using the Nextcloud provider, you must specify the urls via
299+configuration, environment variable, or command line argument. Depending
300+on whether your Nextcloud instance is using pretty urls your urls may be of the
301+form `/index.php/apps/oauth2/*` or `/apps/oauth2/*`.
302+
303+Refer to the [OAuth2
304+documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/oauth2.html)
305+to setup the client id and client secret. Your "Redirection URI" will be
306+`https://internalapp.yourcompany.com/oauth2/callback`.
307+
308+```
309+ -provider nextcloud
310+ -client-id <from nextcloud admin>
311+ -client-secret <from nextcloud admin>
312+ -login-url="<your nextcloud url>/index.php/apps/oauth2/authorize"
313+ -redeem-url="<your nextcloud url>/index.php/apps/oauth2/api/v1/token"
314+ -validate-url="<your nextcloud url>/ocs/v2.php/cloud/user?format=json"
315+```
316+
317+Note: in *all* cases the validate-url will *not* have the `index.php`.
318+
292319## Email Authentication
293320
294321To 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=*`.
providers/nextcloud.gonew file+45
@@ -0,0 +1,45 @@
1+package providers
2+
3+import (
4+ "fmt"
5+ "net/http"
6+
7+ "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
8+ "github.com/pusher/oauth2_proxy/pkg/logger"
9+ "github.com/pusher/oauth2_proxy/pkg/requests"
10+)
11+
12+// NextcloudProvider represents an Nextcloud based Identity Provider
13+type NextcloudProvider struct {
14+ *ProviderData
15+}
16+
17+// NewNextcloudProvider initiates a new NextcloudProvider
18+func NewNextcloudProvider(p *ProviderData) *NextcloudProvider {
19+ p.ProviderName = "Nextcloud"
20+ return &NextcloudProvider{ProviderData: p}
21+}
22+
23+func getNextcloudHeader(accessToken string) http.Header {
24+ header := make(http.Header)
25+ header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
26+ return header
27+}
28+
29+// GetEmailAddress returns the Account email address
30+func (p *NextcloudProvider) GetEmailAddress(s *sessions.SessionState) (string, error) {
31+ req, err := http.NewRequest("GET",
32+ p.ValidateURL.String(), nil)
33+ if err != nil {
34+ logger.Printf("failed building request %s", err)
35+ return "", err
36+ }
37+ req.Header = getNextcloudHeader(s.AccessToken)
38+ json, err := requests.Request(req)
39+ if err != nil {
40+ logger.Printf("failed making request %s", err)
41+ return "", err
42+ }
43+ email, err := json.Get("ocs").Get("data").Get("email").String()
44+ return email, err
45+}
providers/nextcloud_test.gonew file+138
@@ -0,0 +1,138 @@
1+package providers
2+
3+import (
4+ "net/http"
5+ "net/http/httptest"
6+ "net/url"
7+ "testing"
8+
9+ "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
10+ "github.com/stretchr/testify/assert"
11+)
12+
13+const formatJSON = "format=json"
14+const userPath = "/ocs/v2.php/cloud/user"
15+
16+func testNextcloudProvider(hostname string) *NextcloudProvider {
17+ p := NewNextcloudProvider(
18+ &ProviderData{
19+ ProviderName: "",
20+ LoginURL: &url.URL{},
21+ RedeemURL: &url.URL{},
22+ ProfileURL: &url.URL{},
23+ ValidateURL: &url.URL{},
24+ Scope: ""})
25+ if hostname != "" {
26+ updateURL(p.Data().LoginURL, hostname)
27+ updateURL(p.Data().RedeemURL, hostname)
28+ updateURL(p.Data().ProfileURL, hostname)
29+ updateURL(p.Data().ValidateURL, hostname)
30+ }
31+ return p
32+}
33+
34+func testNextcloudBackend(payload string) *httptest.Server {
35+ path := userPath
36+ query := formatJSON
37+
38+ return httptest.NewServer(http.HandlerFunc(
39+ func(w http.ResponseWriter, r *http.Request) {
40+ if r.URL.Path != path || r.URL.RawQuery != query {
41+ w.WriteHeader(404)
42+ } else if r.Header.Get("Authorization") != "Bearer imaginary_access_token_nextcloud" {
43+ w.WriteHeader(403)
44+ } else {
45+ w.WriteHeader(200)
46+ w.Write([]byte(payload))
47+ }
48+ }))
49+}
50+
51+func TestNextcloudProviderDefaults(t *testing.T) {
52+ p := testNextcloudProvider("")
53+ assert.NotEqual(t, nil, p)
54+ assert.Equal(t, "Nextcloud", p.Data().ProviderName)
55+ assert.Equal(t, "",
56+ p.Data().LoginURL.String())
57+ assert.Equal(t, "",
58+ p.Data().RedeemURL.String())
59+ assert.Equal(t, "",
60+ p.Data().ValidateURL.String())
61+}
62+
63+func TestNextcloudProviderOverrides(t *testing.T) {
64+ p := NewNextcloudProvider(
65+ &ProviderData{
66+ LoginURL: &url.URL{
67+ Scheme: "https",
68+ Host: "example.com",
69+ Path: "/index.php/apps/oauth2/authorize"},
70+ RedeemURL: &url.URL{
71+ Scheme: "https",
72+ Host: "example.com",
73+ Path: "/index.php/apps/oauth2/api/v1/token"},
74+ ValidateURL: &url.URL{
75+ Scheme: "https",
76+ Host: "example.com",
77+ Path: "/test/ocs/v2.php/cloud/user",
78+ RawQuery: formatJSON},
79+ Scope: "profile"})
80+ assert.NotEqual(t, nil, p)
81+ assert.Equal(t, "Nextcloud", p.Data().ProviderName)
82+ assert.Equal(t, "https://example.com/index.php/apps/oauth2/authorize",
83+ p.Data().LoginURL.String())
84+ assert.Equal(t, "https://example.com/index.php/apps/oauth2/api/v1/token",
85+ p.Data().RedeemURL.String())
86+ assert.Equal(t, "https://example.com/test/ocs/v2.php/cloud/user?"+formatJSON,
87+ p.Data().ValidateURL.String())
88+}
89+
90+func TestNextcloudProviderGetEmailAddress(t *testing.T) {
91+ b := testNextcloudBackend("{\"ocs\": {\"data\": { \"email\": \"michael.bland@gsa.gov\"}}}")
92+ defer b.Close()
93+
94+ bURL, _ := url.Parse(b.URL)
95+ p := testNextcloudProvider(bURL.Host)
96+ p.ValidateURL.Path = userPath
97+ p.ValidateURL.RawQuery = formatJSON
98+
99+ session := &sessions.SessionState{AccessToken: "imaginary_access_token_nextcloud"}
100+ email, err := p.GetEmailAddress(session)
101+ assert.Equal(t, nil, err)
102+ assert.Equal(t, "michael.bland@gsa.gov", email)
103+}
104+
105+// Note that trying to trigger the "failed building request" case is not
106+// practical, since the only way it can fail is if the URL fails to parse.
107+func TestNextcloudProviderGetEmailAddressFailedRequest(t *testing.T) {
108+ b := testNextcloudBackend("unused payload")
109+ defer b.Close()
110+
111+ bURL, _ := url.Parse(b.URL)
112+ p := testNextcloudProvider(bURL.Host)
113+ p.ValidateURL.Path = userPath
114+ p.ValidateURL.RawQuery = formatJSON
115+
116+ // We'll trigger a request failure by using an unexpected access
117+ // token. Alternatively, we could allow the parsing of the payload as
118+ // JSON to fail.
119+ session := &sessions.SessionState{AccessToken: "unexpected_access_token"}
120+ email, err := p.GetEmailAddress(session)
121+ assert.NotEqual(t, nil, err)
122+ assert.Equal(t, "", email)
123+}
124+
125+func TestNextcloudProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
126+ b := testNextcloudBackend("{\"foo\": \"bar\"}")
127+ defer b.Close()
128+
129+ bURL, _ := url.Parse(b.URL)
130+ p := testNextcloudProvider(bURL.Host)
131+ p.ValidateURL.Path = userPath
132+ p.ValidateURL.RawQuery = formatJSON
133+
134+ session := &sessions.SessionState{AccessToken: "imaginary_access_token_nextcloud"}
135+ email, err := p.GetEmailAddress(session)
136+ assert.NotEqual(t, nil, err)
137+ assert.Equal(t, "", email)
138+}
providers/providers.go+2
@@ -40,6 +40,8 @@ func New(provider string, p *ProviderData) Provider {
4040 return NewLoginGovProvider(p)
4141 case "bitbucket":
4242 return NewBitbucketProvider(p)
43+ case "nextcloud":
44+ return NewNextcloudProvider(p)
4345 default:
4446 return NewGoogleProvider(p)
4547 }