From b794248176310c0b33c6d6e4151f44921a9fb892 Mon Sep 17 00:00:00 2001 From: zv0n Date: Sun, 24 Apr 2022 03:11:38 +0200 Subject: [PATCH] Add allowed_emails option to the auth endpoint query string (#1595) * Add allowed_emails option to the auth endpoint query string * Don't return true from checkAllowedEmailsOrDomains only because domains field was empty * Fix checkAllowedEmailsOrDomains logic * Added tests for allowed_emails query parameter * Updated CHANGELOG * Remove checkAllowedEmailsOrDomains Co-authored-by: Nick Meves --- CHANGELOG.md | 1 + docs/docs/features/endpoints.md | 3 +- oauthproxy.go | 21 ++++++++++ oauthproxy_test.go | 69 ++++++++++++++++++++++++++++++++- 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 831cd190..698141f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ If you are using an architecture specific tag (ex: v7.2.1-arm64) you should move ## Changes since v7.2.1 +- [#1595](https://github.com/oauth2-proxy/oauth2-proxy/pull/1595) Add optional `allowed_emails` query parameter to the `auth_request`. (@zv0n) - [#1478](https://github.com/oauth2-proxy/oauth2-proxy/pull/1478) Parameterise the runtime image (@omBratteng) - [#1583](https://github.com/oauth2-proxy/oauth2-proxy/pull/1583) Add groups to session too when creating session from bearer token (@adriananeci) - [#1418](https://github.com/oauth2-proxy/oauth2-proxy/pull/1418) Support for passing arbitrary query parameters through from `/oauth2/start` to the identity provider's login URL. Configuration settings control which parameters are passed by default and precisely which values can be overridden per-request (@ianroberts) diff --git a/docs/docs/features/endpoints.md b/docs/docs/features/endpoints.md index 4832a5de..61e1107b 100644 --- a/docs/docs/features/endpoints.md +++ b/docs/docs/features/endpoints.md @@ -41,4 +41,5 @@ This endpoint returns 202 Accepted response or a 401 Unauthorized response. It can be configured using the following query parameters query parameters: - `allowed_groups`: comma separated list of allowed groups -- `allowed_email_domains`: comma separated list of allowed email domains \ No newline at end of file +- `allowed_email_domains`: comma separated list of allowed email domains +- `allowed_emails`: comma separated list of allowed emails \ No newline at end of file diff --git a/oauthproxy.go b/oauthproxy.go index 308f806b..a571fcab 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -1021,6 +1021,7 @@ func authOnlyAuthorize(req *http.Request, s *sessionsapi.SessionState) bool { constraints := []func(*http.Request, *sessionsapi.SessionState) bool{ checkAllowedGroups, checkAllowedEmailDomains, + checkAllowedEmails, } for _, constraint := range constraints { @@ -1091,6 +1092,26 @@ func checkAllowedGroups(req *http.Request, s *sessionsapi.SessionState) bool { return false } +// checkAllowedEmails allow email restrictions based on the `allowed_emails` +// querystring parameter +func checkAllowedEmails(req *http.Request, s *sessionsapi.SessionState) bool { + allowedEmails := extractAllowedEntities(req, "allowed_emails") + if len(allowedEmails) == 0 { + return true + } + + allowed := false + + for email := range allowedEmails { + if email == s.Email { + allowed = true + break + } + } + + return allowed +} + // encodedState builds the OAuth state param out of our nonce and // original application redirect func encodeState(nonce string, redirect string) string { diff --git a/oauthproxy_test.go b/oauthproxy_test.go index 90b27d59..25d23b45 100644 --- a/oauthproxy_test.go +++ b/oauthproxy_test.go @@ -2725,7 +2725,7 @@ func TestAuthOnlyAllowedEmailDomains(t *testing.T) { expectedStatusCode: http.StatusForbidden, }, { - name: "UserInAllowedEmailDomains", + name: "UserNotInAllowedEmailDomains", email: "toto@example.com", querystring: "?allowed_email_domains=a.example.com,b.example.com", expectedStatusCode: http.StatusForbidden, @@ -2789,3 +2789,70 @@ func TestAuthOnlyAllowedEmailDomains(t *testing.T) { }) } } + +func TestAuthOnlyAllowedEmails(t *testing.T) { + testCases := []struct { + name string + email string + querystring string + expectedStatusCode int + }{ + { + name: "NotEmailRestriction", + email: "toto@example.com", + querystring: "", + expectedStatusCode: http.StatusAccepted, + }, + { + name: "UserInAllowedEmail", + email: "toto@example.com", + querystring: "?allowed_emails=toto@example.com", + expectedStatusCode: http.StatusAccepted, + }, + { + name: "UserNotInAllowedEmail", + email: "toto@example.com", + querystring: "?allowed_emails=tete@example.com", + expectedStatusCode: http.StatusForbidden, + }, + { + name: "UserNotInAllowedEmails", + email: "toto@example.com", + querystring: "?allowed_emails=tete@example.com,tutu@example.com", + expectedStatusCode: http.StatusForbidden, + }, + { + name: "UserInAllowedEmails", + email: "toto@example.com", + querystring: "?allowed_emails=tete@example.com,toto@example.com", + expectedStatusCode: http.StatusAccepted, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + groups := []string{} + + created := time.Now() + + session := &sessions.SessionState{ + Groups: groups, + Email: tc.email, + AccessToken: "oauth_token", + CreatedAt: &created, + } + + test, err := NewAuthOnlyEndpointTest(tc.querystring, func(opts *options.Options) {}) + if err != nil { + t.Fatal(err) + } + + err = test.SaveSession(session) + assert.NoError(t, err) + + test.proxy.ServeHTTP(test.rw, test.req) + + assert.Equal(t, tc.expectedStatusCode, test.rw.Code) + }) + } +}