Merge pull request #1 from oauth2-proxy/master

Update Fork
This commit is contained in:
Kevin Kreitner 2020-09-30 18:25:27 +02:00 committed by GitHub
commit d4745fda9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 861 additions and 306 deletions

View File

@ -4,11 +4,31 @@
## Important Notes ## Important Notes
- [#575](https://github.com/oauth2-proxy/oauth2-proxy/pull/575) Sessions from v5.1.1 or earlier will no longer validate since they were not signed with SHA1.
- Sessions from v6.0.0 or later had a graceful conversion to SHA256 that resulted in no reauthentication
- Upgrading from v5.1.1 or earlier will result in a reauthentication
- [#616](https://github.com/oauth2-proxy/oauth2-proxy/pull/616) Ensure you have configured oauth2-proxy to use the `groups` scope. The user may be logged out initially as they may not currently have the `groups` claim however after going back through login process wil be authenticated.
## Breaking Changes ## Breaking Changes
- [#722](https://github.com/oauth2-proxy/oauth2-proxy/pull/722) When a Redis session store is configured, OAuth2-Proxy will fail to start up unless connection and health checks to Redis pass
- [#800](https://github.com/oauth2-proxy/oauth2-proxy/pull/800) Fix import path for v7. The import path has changed to support the go get installation.
- You can now `go get github.com/oauth2-proxy/oauth2-proxy/v7` to get the latest `v7` version of OAuth2 Proxy
- Import paths for package are now under `v7`, eg `github.com/oauth2-proxy/oauth2-proxy/v7/pkg/<module>`
- [#753](https://github.com/oauth2-proxy/oauth2-proxy/pull/753) A bug in the Azure provider prevented it from properly passing the configured protected `--resource`
via the login url. If this option was used in the past, behavior will change with this release as it will
affect the tokens returned by Azure. In the past, the tokens were always for `https://graph.microsoft.com` (the default)
and will now be for the configured resource (if it exists, otherwise it will run into errors)
## Changes since v6.1.1 ## Changes since v6.1.1
- [#753](https://github.com/oauth2-proxy/oauth2-proxy/pull/753) Pass resource parameter in login url (@codablock)
- [#575](https://github.com/oauth2-proxy/oauth2-proxy/pull/575) Stop accepting legacy SHA1 signed cookies (@NickMeves)
- [#722](https://github.com/oauth2-proxy/oauth2-proxy/pull/722) Validate Redis configuration options at startup (@NickMeves)
- [#791](https://github.com/oauth2-proxy/oauth2-proxy/pull/791) Remove GetPreferredUsername method from provider interface (@NickMeves)
- [#764](https://github.com/oauth2-proxy/oauth2-proxy/pull/764) Document bcrypt encryption for htpasswd (and hide SHA) (@lentzi90) - [#764](https://github.com/oauth2-proxy/oauth2-proxy/pull/764) Document bcrypt encryption for htpasswd (and hide SHA) (@lentzi90)
- [#616](https://github.com/oauth2-proxy/oauth2-proxy/pull/616) Add support to ensure user belongs in required groups when using the OIDC provider (@stefansedich)
- [#800](https://github.com/oauth2-proxy/oauth2-proxy/pull/800) Fix import path for v7 (@johejo)
# v6.1.1 # v6.1.1

View File

@ -11,7 +11,7 @@ nav_order: 1
a. Download [Prebuilt Binary](https://github.com/oauth2-proxy/oauth2-proxy/releases) (current release is `v6.1.1`) a. Download [Prebuilt Binary](https://github.com/oauth2-proxy/oauth2-proxy/releases) (current release is `v6.1.1`)
b. Build with `$ go get github.com/oauth2-proxy/oauth2-proxy` which will put the binary in `$GOROOT/bin` b. Build with `$ go get github.com/oauth2-proxy/oauth2-proxy/v7` which will put the binary in `$GOPATH/bin`
c. Using the prebuilt docker image [quay.io/oauth2-proxy/oauth2-proxy](https://quay.io/oauth2-proxy/oauth2-proxy) (AMD64, ARMv6 and ARM64 tags available) c. Using the prebuilt docker image [quay.io/oauth2-proxy/oauth2-proxy](https://quay.io/oauth2-proxy/oauth2-proxy) (AMD64, ARMv6 and ARM64 tags available)

View File

@ -142,9 +142,9 @@ Make sure you set the following to the appropriate url:
-provider=keycloak -provider=keycloak
-client-id=<client you have created> -client-id=<client you have created>
-client-secret=<your client's secret> -client-secret=<your client's secret>
-login-url="http(s)://<keycloak host>/realms/<your realm>/protocol/openid-connect/auth" -login-url="http(s)://<keycloak host>/auth/realms/<your realm>/protocol/openid-connect/auth"
-redeem-url="http(s)://<keycloak host>/realms/<your realm>/protocol/openid-connect/token" -redeem-url="http(s)://<keycloak host>/auth/realms/<your realm>/protocol/openid-connect/token"
-validate-url="http(s)://<keycloak host>/realms/<your realm>/protocol/openid-connect/userinfo" -validate-url="http(s)://<keycloak host>/auth/realms/<your realm>/protocol/openid-connect/userinfo"
-keycloak-group=<user_group> -keycloak-group=<user_group>
The group management in keycloak is using a tree. If you create a group named admin in keycloak you should define the 'keycloak-group' value to /admin. The group management in keycloak is using a tree. If you create a group named admin in keycloak you should define the 'keycloak-group' value to /admin.

View File

@ -78,12 +78,13 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
| `--insecure-oidc-skip-issuer-verification` | bool | allow the OIDC issuer URL to differ from the expected (currently required for Azure multi-tenant compatibility) | false | | `--insecure-oidc-skip-issuer-verification` | bool | allow the OIDC issuer URL to differ from the expected (currently required for Azure multi-tenant compatibility) | false |
| `--oidc-issuer-url` | string | the OpenID Connect issuer URL, e.g. `"https://accounts.google.com"` | | | `--oidc-issuer-url` | string | the OpenID Connect issuer URL, e.g. `"https://accounts.google.com"` | |
| `--oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | | | `--oidc-jwks-url` | string | OIDC JWKS URI for token verification; required if OIDC discovery is disabled | |
| `--oidc-groups-claim` | string | which claim contains the user groups | `"groups"` |
| `--pass-access-token` | bool | pass OAuth access_token to upstream via X-Forwarded-Access-Token header | false | | `--pass-access-token` | bool | pass OAuth access_token to upstream via X-Forwarded-Access-Token header | false |
| `--pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false | | `--pass-authorization-header` | bool | pass OIDC IDToken to upstream via Authorization Bearer header | false |
| `--pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true | | `--pass-basic-auth` | bool | pass HTTP Basic Auth, X-Forwarded-User, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true |
| `--prefer-email-to-user` | bool | Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, e.g. htaccess authentication. Used in conjunction with `--pass-basic-auth` and `--pass-user-headers` | false | | `--prefer-email-to-user` | bool | Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, e.g. htaccess authentication. Used in conjunction with `--pass-basic-auth` and `--pass-user-headers` | false |
| `--pass-host-header` | bool | pass the request Host Header to upstream | true | | `--pass-host-header` | bool | pass the request Host Header to upstream | true |
| `--pass-user-headers` | bool | pass X-Forwarded-User, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true | | `--pass-user-headers` | bool | pass X-Forwarded-User, X-Forwarded-Groups, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream | true |
| `--profile-url` | string | Profile access endpoint | | | `--profile-url` | string | Profile access endpoint | |
| `--prompt` | string | [OIDC prompt](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest); if present, `approval-prompt` is ignored | `""` | | `--prompt` | string | [OIDC prompt](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest); if present, `approval-prompt` is ignored | `""` |
| `--provider` | string | OAuth provider | google | | `--provider` | string | OAuth provider | google |
@ -112,7 +113,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
| `--scope` | string | OAuth scope specification | | | `--scope` | string | OAuth scope specification | |
| `--session-cookie-minimal` | bool | strip OAuth tokens from cookie session stores if they aren't needed (cookie session store only) | false | | `--session-cookie-minimal` | bool | strip OAuth tokens from cookie session stores if they aren't needed (cookie session store only) | false |
| `--session-store-type` | string | [Session data storage backend](configuration/sessions); redis or cookie | cookie | | `--session-store-type` | string | [Session data storage backend](configuration/sessions); redis or cookie | cookie |
| `--set-xauthrequest` | bool | set X-Auth-Request-User, X-Auth-Request-Email and X-Auth-Request-Preferred-Username response headers (useful in Nginx auth_request mode) | false | | `--set-xauthrequest` | bool | set X-Auth-Request-User, X-Auth-Request-Groups, X-Auth-Request-Email and X-Auth-Request-Preferred-Username response headers (useful in Nginx auth_request mode) | false |
| `--set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false | | `--set-authorization-header` | bool | set Authorization Bearer response header (useful in Nginx auth_request mode) | false |
| `--set-basic-auth` | bool | set HTTP Basic Auth information in response (useful in Nginx auth_request mode) | false | | `--set-basic-auth` | bool | set HTTP Basic Auth information in response (useful in Nginx auth_request mode) | false |
| `--signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | | | `--signature-key` | string | GAP-Signature request signature key (algorithm:secretkey) | |
@ -131,6 +132,7 @@ An example [oauth2-proxy.cfg]({{ site.gitweb }}/contrib/oauth2-proxy.cfg.example
| `--tls-key-file` | string | path to private key file | | | `--tls-key-file` | string | path to private key file | |
| `--upstream` | string \| list | the http url(s) of the upstream endpoint, file:// paths for static files or `static://<status_code>` for static response. Routing is based on the path | | | `--upstream` | string \| list | the http url(s) of the upstream endpoint, file:// paths for static files or `static://<status_code>` for static response. Routing is based on the path | |
| `--user-id-claim` | string | which claim contains the user ID | \["email"\] | | `--user-id-claim` | string | which claim contains the user ID | \["email"\] |
| `--allowed-group` | string \| list | restrict logins to members of this group (may be given multiple times) | |
| `--validate-url` | string | Access token validation endpoint | | | `--validate-url` | string | Access token validation endpoint | |
| `--version` | n/a | print version string | | | `--version` | n/a | print version string | |
| `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (e.g. `.example.com`)&nbsp;\[[2](#footnote2)\] | | | `--whitelist-domain` | string \| list | allowed domains for redirection after authentication. Prefix domain with a `.` to allow subdomains (e.g. `.example.com`)&nbsp;\[[2](#footnote2)\] | |

2
go.mod
View File

@ -1,4 +1,4 @@
module github.com/oauth2-proxy/oauth2-proxy module github.com/oauth2-proxy/oauth2-proxy/v7
go 1.14 go 1.14

6
go.sum
View File

@ -150,15 +150,11 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
@ -295,8 +291,6 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=

View File

@ -9,8 +9,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
// Server represents an HTTP server // Server represents an HTTP server

View File

@ -6,7 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -10,7 +10,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status // responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP status

View File

@ -6,7 +6,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -11,10 +11,10 @@ import (
"time" "time"
"github.com/justinas/alice" "github.com/justinas/alice"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
"github.com/oauth2-proxy/oauth2-proxy/pkg/validation" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
) )
func main() { func main() {

View File

@ -16,20 +16,20 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/justinas/alice" "github.com/justinas/alice"
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip" ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/middleware" middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/authentication/basic" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/basic"
"github.com/oauth2-proxy/oauth2-proxy/pkg/cookies" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
"github.com/oauth2-proxy/oauth2-proxy/pkg/ip" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/middleware" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/upstream" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
"github.com/oauth2-proxy/oauth2-proxy/pkg/util" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
"github.com/oauth2-proxy/oauth2-proxy/providers" "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
) )
const ( const (
@ -102,6 +102,7 @@ type OAuthProxy struct {
trustedIPs *ip.NetSet trustedIPs *ip.NetSet
Banner string Banner string
Footer string Footer string
AllowedGroups []string
sessionChain alice.Chain sessionChain alice.Chain
} }
@ -215,6 +216,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
Banner: opts.Banner, Banner: opts.Banner,
Footer: opts.Footer, Footer: opts.Footer,
SignInMessage: buildSignInMessage(opts), SignInMessage: buildSignInMessage(opts),
AllowedGroups: opts.AllowedGroups,
basicAuthValidator: basicAuthValidator, basicAuthValidator: basicAuthValidator,
displayHtpasswdForm: basicAuthValidator != nil, displayHtpasswdForm: basicAuthValidator != nil,
@ -294,34 +296,31 @@ func (p *OAuthProxy) GetRedirectURI(host string) string {
return u.String() return u.String()
} }
func (p *OAuthProxy) redeemCode(ctx context.Context, host, code string) (s *sessionsapi.SessionState, err error) { func (p *OAuthProxy) redeemCode(ctx context.Context, host, code string) (*sessionsapi.SessionState, error) {
if code == "" { if code == "" {
return nil, errors.New("missing code") return nil, errors.New("missing code")
} }
redirectURI := p.GetRedirectURI(host) redirectURI := p.GetRedirectURI(host)
s, err = p.provider.Redeem(ctx, redirectURI, code) s, err := p.provider.Redeem(ctx, redirectURI, code)
if err != nil { if err != nil {
return return nil, err
} }
if s.Email == "" { if s.Email == "" {
s.Email, err = p.provider.GetEmailAddress(ctx, s) s.Email, err = p.provider.GetEmailAddress(ctx, s)
} if err != nil && err.Error() != "not implemented" {
return nil, err
if s.PreferredUsername == "" {
s.PreferredUsername, err = p.provider.GetPreferredUsername(ctx, s)
if err != nil && err.Error() == "not implemented" {
err = nil
} }
} }
if s.User == "" { if s.User == "" {
s.User, err = p.provider.GetUserName(ctx, s) s.User, err = p.provider.GetUserName(ctx, s)
if err != nil && err.Error() == "not implemented" { if err != nil && err.Error() != "not implemented" {
err = nil return nil, err
} }
} }
return
return s, nil
} }
// MakeCSRFCookie creates a cookie for CSRF // MakeCSRFCookie creates a cookie for CSRF
@ -888,7 +887,10 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
return nil, ErrNeedsLogin return nil, ErrNeedsLogin
} }
if session != nil && session.Email != "" && !p.Validator(session.Email) { invalidEmail := session != nil && session.Email != "" && !p.Validator(session.Email)
invalidGroups := session != nil && !p.validateGroups(session.Groups)
if invalidEmail || invalidGroups {
logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session) logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session)
// Invalid session, clear it // Invalid session, clear it
err := p.ClearSessionCookie(rw, req) err := p.ClearSessionCookie(rw, req)
@ -942,6 +944,14 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
} else { } else {
req.Header.Del("X-Forwarded-Preferred-Username") req.Header.Del("X-Forwarded-Preferred-Username")
} }
if len(session.Groups) > 0 {
for _, group := range session.Groups {
req.Header.Add("X-Forwarded-Groups", group)
}
} else {
req.Header.Del("X-Forwarded-Groups")
}
} }
if p.SetXAuthRequest { if p.SetXAuthRequest {
@ -964,6 +974,14 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
rw.Header().Del("X-Auth-Request-Access-Token") rw.Header().Del("X-Auth-Request-Access-Token")
} }
} }
if len(session.Groups) > 0 {
for _, group := range session.Groups {
rw.Header().Add("X-Auth-Request-Groups", group)
}
} else {
rw.Header().Del("X-Auth-Request-Groups")
}
} }
if p.PassAccessToken { if p.PassAccessToken {
@ -1012,6 +1030,7 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
func (p *OAuthProxy) stripAuthHeaders(req *http.Request) { func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
if p.PassBasicAuth { if p.PassBasicAuth {
req.Header.Del("X-Forwarded-User") req.Header.Del("X-Forwarded-User")
req.Header.Del("X-Forwarded-Groups")
req.Header.Del("X-Forwarded-Email") req.Header.Del("X-Forwarded-Email")
req.Header.Del("X-Forwarded-Preferred-Username") req.Header.Del("X-Forwarded-Preferred-Username")
req.Header.Del("Authorization") req.Header.Del("Authorization")
@ -1019,6 +1038,7 @@ func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
if p.PassUserHeaders { if p.PassUserHeaders {
req.Header.Del("X-Forwarded-User") req.Header.Del("X-Forwarded-User")
req.Header.Del("X-Forwarded-Groups")
req.Header.Del("X-Forwarded-Email") req.Header.Del("X-Forwarded-Email")
req.Header.Del("X-Forwarded-Preferred-Username") req.Header.Del("X-Forwarded-Preferred-Username")
} }
@ -1049,3 +1069,23 @@ func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) {
rw.Header().Set("Content-Type", applicationJSON) rw.Header().Set("Content-Type", applicationJSON)
rw.WriteHeader(code) rw.WriteHeader(code)
} }
func (p *OAuthProxy) validateGroups(groups []string) bool {
if len(p.AllowedGroups) == 0 {
return true
}
allowedGroups := map[string]struct{}{}
for _, group := range p.AllowedGroups {
allowedGroups[group] = struct{}{}
}
for _, group := range groups {
if _, ok := allowedGroups[group]; ok {
return true
}
}
return false
}

View File

@ -19,13 +19,13 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/mbland/hmacauth" "github.com/mbland/hmacauth"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
sessionscookie "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie" sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
"github.com/oauth2-proxy/oauth2-proxy/pkg/upstream" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
"github.com/oauth2-proxy/oauth2-proxy/pkg/validation" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/validation"
"github.com/oauth2-proxy/oauth2-proxy/providers" "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -592,6 +592,37 @@ func TestPassUserHeadersWithEmail(t *testing.T) {
} }
} }
func TestPassGroupsHeadersWithGroups(t *testing.T) {
opts := baseTestOptions()
err := validation.Validate(opts)
assert.NoError(t, err)
const emailAddress = "john.doe@example.com"
const userName = "9fcab5c9b889a557"
groups := []string{"a", "b"}
created := time.Now()
session := &sessions.SessionState{
User: userName,
Groups: groups,
Email: emailAddress,
AccessToken: "oauth_token",
CreatedAt: &created,
}
{
rw := httptest.NewRecorder()
req, _ := http.NewRequest("GET", opts.ProxyPrefix+"/testCase0", nil)
proxy, err := NewOAuthProxy(opts, func(email string) bool {
return email == emailAddress
})
if err != nil {
t.Fatal(err)
}
proxy.addHeadersForProxying(rw, req, session)
assert.Equal(t, groups, req.Header["X-Forwarded-Groups"])
}
}
func TestStripAuthHeaders(t *testing.T) { func TestStripAuthHeaders(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
SkipAuthStripHeaders bool SkipAuthStripHeaders bool
@ -609,6 +640,7 @@ func TestStripAuthHeaders(t *testing.T) {
PassAuthorization: false, PassAuthorization: false,
StrippedHeaders: map[string]bool{ StrippedHeaders: map[string]bool{
"X-Forwarded-User": true, "X-Forwarded-User": true,
"X-Forwared-Groups": true,
"X-Forwarded-Email": true, "X-Forwarded-Email": true,
"X-Forwarded-Preferred-Username": true, "X-Forwarded-Preferred-Username": true,
"X-Forwarded-Access-Token": false, "X-Forwarded-Access-Token": false,
@ -623,6 +655,7 @@ func TestStripAuthHeaders(t *testing.T) {
PassAuthorization: false, PassAuthorization: false,
StrippedHeaders: map[string]bool{ StrippedHeaders: map[string]bool{
"X-Forwarded-User": true, "X-Forwarded-User": true,
"X-Forwared-Groups": true,
"X-Forwarded-Email": true, "X-Forwarded-Email": true,
"X-Forwarded-Preferred-Username": true, "X-Forwarded-Preferred-Username": true,
"X-Forwarded-Access-Token": true, "X-Forwarded-Access-Token": true,
@ -637,6 +670,7 @@ func TestStripAuthHeaders(t *testing.T) {
PassAuthorization: false, PassAuthorization: false,
StrippedHeaders: map[string]bool{ StrippedHeaders: map[string]bool{
"X-Forwarded-User": true, "X-Forwarded-User": true,
"X-Forwared-Groups": true,
"X-Forwarded-Email": true, "X-Forwarded-Email": true,
"X-Forwarded-Preferred-Username": true, "X-Forwarded-Preferred-Username": true,
"X-Forwarded-Access-Token": true, "X-Forwarded-Access-Token": true,
@ -651,6 +685,7 @@ func TestStripAuthHeaders(t *testing.T) {
PassAuthorization: true, PassAuthorization: true,
StrippedHeaders: map[string]bool{ StrippedHeaders: map[string]bool{
"X-Forwarded-User": false, "X-Forwarded-User": false,
"X-Forwared-Groups": false,
"X-Forwarded-Email": false, "X-Forwarded-Email": false,
"X-Forwarded-Preferred-Username": false, "X-Forwarded-Preferred-Username": false,
"X-Forwarded-Access-Token": false, "X-Forwarded-Access-Token": false,
@ -665,6 +700,7 @@ func TestStripAuthHeaders(t *testing.T) {
PassAuthorization: false, PassAuthorization: false,
StrippedHeaders: map[string]bool{ StrippedHeaders: map[string]bool{
"X-Forwarded-User": false, "X-Forwarded-User": false,
"X-Forwared-Groups": false,
"X-Forwarded-Email": false, "X-Forwarded-Email": false,
"X-Forwarded-Preferred-Username": false, "X-Forwarded-Preferred-Username": false,
"X-Forwarded-Access-Token": false, "X-Forwarded-Access-Token": false,
@ -679,6 +715,7 @@ func TestStripAuthHeaders(t *testing.T) {
PassAuthorization: false, PassAuthorization: false,
StrippedHeaders: map[string]bool{ StrippedHeaders: map[string]bool{
"X-Forwarded-User": false, "X-Forwarded-User": false,
"X-Forwared-Groups": false,
"X-Forwarded-Email": false, "X-Forwarded-Email": false,
"X-Forwarded-Preferred-Username": false, "X-Forwarded-Preferred-Username": false,
"X-Forwarded-Access-Token": false, "X-Forwarded-Access-Token": false,
@ -690,6 +727,7 @@ func TestStripAuthHeaders(t *testing.T) {
initialHeaders := map[string]string{ initialHeaders := map[string]string{
"X-Forwarded-User": "9fcab5c9b889a557", "X-Forwarded-User": "9fcab5c9b889a557",
"X-Forwarded-Email": "john.doe@example.com", "X-Forwarded-Email": "john.doe@example.com",
"X-Forwarded-Groups": "a,b,c",
"X-Forwarded-Preferred-Username": "john.doe", "X-Forwarded-Preferred-Username": "john.doe",
"X-Forwarded-Access-Token": "AccessToken", "X-Forwarded-Access-Token": "AccessToken",
"Authorization": "bearer IDToken", "Authorization": "bearer IDToken",
@ -1333,6 +1371,7 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
pcTest.opts = baseTestOptions() pcTest.opts = baseTestOptions()
pcTest.opts.SetXAuthRequest = true pcTest.opts.SetXAuthRequest = true
pcTest.opts.AllowedGroups = []string{"oauth_groups"}
err := validation.Validate(pcTest.opts) err := validation.Validate(pcTest.opts)
assert.NoError(t, err) assert.NoError(t, err)
@ -1354,13 +1393,14 @@ func TestAuthOnlyEndpointSetXAuthRequestHeaders(t *testing.T) {
created := time.Now() created := time.Now()
startSession := &sessions.SessionState{ startSession := &sessions.SessionState{
User: "oauth_user", Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created} User: "oauth_user", Groups: []string{"oauth_groups"}, Email: "oauth_user@example.com", AccessToken: "oauth_token", CreatedAt: &created}
err = pcTest.SaveSession(startSession) err = pcTest.SaveSession(startSession)
assert.NoError(t, err) assert.NoError(t, err)
pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req) pcTest.proxy.ServeHTTP(pcTest.rw, pcTest.req)
assert.Equal(t, http.StatusAccepted, pcTest.rw.Code) assert.Equal(t, http.StatusAccepted, pcTest.rw.Code)
assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User")) assert.Equal(t, "oauth_user", pcTest.rw.Header().Get("X-Auth-Request-User"))
assert.Equal(t, startSession.Groups, pcTest.rw.Header().Values("X-Auth-Request-Groups"))
assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email")) assert.Equal(t, "oauth_user@example.com", pcTest.rw.Header().Get("X-Auth-Request-Email"))
} }
@ -2199,3 +2239,108 @@ func TestTrustedIPs(t *testing.T) {
}) })
} }
} }
func TestProxyAllowedGroups(t *testing.T) {
tests := []struct {
name string
allowedGroups []string
groups []string
expectUnauthorized bool
}{
{"NoAllowedGroups", []string{}, []string{}, false},
{"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
{"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
{"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
emailAddress := "test"
created := time.Now()
session := &sessions.SessionState{
Groups: tt.groups,
Email: emailAddress,
AccessToken: "oauth_token",
CreatedAt: &created,
}
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))
t.Cleanup(upstream.Close)
test, err := NewProcessCookieTestWithOptionsModifiers(func(opts *options.Options) {
opts.AllowedGroups = tt.allowedGroups
opts.UpstreamServers = options.Upstreams{
{
ID: upstream.URL,
Path: "/",
URI: upstream.URL,
},
}
})
if err != nil {
t.Fatal(err)
}
test.req, _ = http.NewRequest("GET", "/", nil)
test.req.Header.Add("accept", applicationJSON)
test.SaveSession(session)
test.proxy.ServeHTTP(test.rw, test.req)
if tt.expectUnauthorized {
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
} else {
assert.Equal(t, http.StatusOK, test.rw.Code)
}
})
}
}
func TestAuthOnlyAllowedGroups(t *testing.T) {
tests := []struct {
name string
allowedGroups []string
groups []string
expectUnauthorized bool
}{
{"NoAllowedGroups", []string{}, []string{}, false},
{"NoAllowedGroupsUserHasGroups", []string{}, []string{"a", "b"}, false},
{"UserInAllowedGroup", []string{"a"}, []string{"a", "b"}, false},
{"UserNotInAllowedGroup", []string{"a"}, []string{"c"}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
emailAddress := "test"
created := time.Now()
session := &sessions.SessionState{
Groups: tt.groups,
Email: emailAddress,
AccessToken: "oauth_token",
CreatedAt: &created,
}
test, err := NewAuthOnlyEndpointTest(func(opts *options.Options) {
opts.AllowedGroups = tt.allowedGroups
})
if err != nil {
t.Fatal(err)
}
err = test.SaveSession(session)
assert.NoError(t, err)
test.proxy.ServeHTTP(test.rw, test.req)
if tt.expectUnauthorized {
assert.Equal(t, http.StatusUnauthorized, test.rw.Code)
} else {
assert.Equal(t, http.StatusAccepted, test.rw.Code)
}
})
}
}

View File

@ -1,7 +1,7 @@
package middleware package middleware
import ( import (
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
) )
// RequestScope contains information regarding the request that is being made. // RequestScope contains information regarding the request that is being made.

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
) )
// TokenToSessionFunc takes a rawIDToken and an idToken and converts it into a // TokenToSessionFunc takes a rawIDToken and an idToken and converts it into a

View File

@ -7,7 +7,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )

View File

@ -1,7 +1,7 @@
package options package options
import ( import (
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )

View File

@ -6,8 +6,8 @@ import (
"regexp" "regexp"
oidc "github.com/coreos/go-oidc" oidc "github.com/coreos/go-oidc"
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip" ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
"github.com/oauth2-proxy/oauth2-proxy/providers" "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
@ -93,6 +93,7 @@ type Options struct {
InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification"` InsecureOIDCSkipIssuerVerification bool `flag:"insecure-oidc-skip-issuer-verification" cfg:"insecure_oidc_skip_issuer_verification"`
SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery"` SkipOIDCDiscovery bool `flag:"skip-oidc-discovery" cfg:"skip_oidc_discovery"`
OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url"` OIDCJwksURL string `flag:"oidc-jwks-url" cfg:"oidc_jwks_url"`
OIDCGroupsClaim string `flag:"oidc-groups-claim" cfg:"oidc_groups_claim"`
LoginURL string `flag:"login-url" cfg:"login_url"` LoginURL string `flag:"login-url" cfg:"login_url"`
RedeemURL string `flag:"redeem-url" cfg:"redeem_url"` RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
ProfileURL string `flag:"profile-url" cfg:"profile_url"` ProfileURL string `flag:"profile-url" cfg:"profile_url"`
@ -102,6 +103,7 @@ type Options struct {
Prompt string `flag:"prompt" cfg:"prompt"` Prompt string `flag:"prompt" cfg:"prompt"`
ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0 ApprovalPrompt string `flag:"approval-prompt" cfg:"approval_prompt"` // Deprecated by OIDC 1.0
UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"` UserIDClaim string `flag:"user-id-claim" cfg:"user_id_claim"`
AllowedGroups []string `flag:"allowed-group" cfg:"allowed_groups"`
SignatureKey string `flag:"signature-key" cfg:"signature_key"` SignatureKey string `flag:"signature-key" cfg:"signature_key"`
AcrValues string `flag:"acr-values" cfg:"acr_values"` AcrValues string `flag:"acr-values" cfg:"acr_values"`
@ -167,6 +169,7 @@ func NewOptions() *Options {
InsecureOIDCAllowUnverifiedEmail: false, InsecureOIDCAllowUnverifiedEmail: false,
SkipOIDCDiscovery: false, SkipOIDCDiscovery: false,
Logging: loggingDefaults(), Logging: loggingDefaults(),
OIDCGroupsClaim: "groups",
} }
} }
@ -248,6 +251,7 @@ func NewFlagSet() *pflag.FlagSet {
flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL") flagSet.Bool("insecure-oidc-skip-issuer-verification", false, "Do not verify if issuer matches OIDC discovery URL")
flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints") flagSet.Bool("skip-oidc-discovery", false, "Skip OIDC discovery and use manually supplied Endpoints")
flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)") flagSet.String("oidc-jwks-url", "", "OpenID Connect JWKS URL (ie: https://www.googleapis.com/oauth2/v3/certs)")
flagSet.String("oidc-groups-claim", "groups", "which claim contains the user groups")
flagSet.String("login-url", "", "Authentication endpoint") flagSet.String("login-url", "", "Authentication endpoint")
flagSet.String("redeem-url", "", "Token redemption endpoint") flagSet.String("redeem-url", "", "Token redemption endpoint")
flagSet.String("profile-url", "", "Profile access endpoint") flagSet.String("profile-url", "", "Profile access endpoint")
@ -265,6 +269,7 @@ func NewFlagSet() *pflag.FlagSet {
flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints") flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints")
flagSet.String("user-id-claim", "email", "which claim contains the user ID") flagSet.String("user-id-claim", "email", "which claim contains the user ID")
flagSet.StringSlice("allowed-group", []string{}, "restrict logins to members of this group (may be given multiple times)")
flagSet.AddFlagSet(cookieFlagSet()) flagSet.AddFlagSet(cookieFlagSet())
flagSet.AddFlagSet(loggingFlagSet()) flagSet.AddFlagSet(loggingFlagSet())

View File

@ -3,7 +3,7 @@ package options
import ( import (
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -7,10 +7,11 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"reflect"
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
"github.com/pierrec/lz4" "github.com/pierrec/lz4"
"github.com/vmihailenco/msgpack/v4" "github.com/vmihailenco/msgpack/v4"
) )
@ -24,6 +25,7 @@ type SessionState struct {
RefreshToken string `json:",omitempty" msgpack:"rt,omitempty"` RefreshToken string `json:",omitempty" msgpack:"rt,omitempty"`
Email string `json:",omitempty" msgpack:"e,omitempty"` Email string `json:",omitempty" msgpack:"e,omitempty"`
User string `json:",omitempty" msgpack:"u,omitempty"` User string `json:",omitempty" msgpack:"u,omitempty"`
Groups []string `json:",omitempty" msgpack:"g,omitempty"`
PreferredUsername string `json:",omitempty" msgpack:"pu,omitempty"` PreferredUsername string `json:",omitempty" msgpack:"pu,omitempty"`
} }
@ -61,6 +63,9 @@ func (s *SessionState) String() string {
if s.RefreshToken != "" { if s.RefreshToken != "" {
o += " refresh_token:true" o += " refresh_token:true"
} }
if len(s.Groups) > 0 {
o += fmt.Sprintf(" groups:%v", s.Groups)
}
return o + "}" return o + "}"
} }
@ -233,7 +238,7 @@ func (s *SessionState) validate() error {
} }
empty := new(SessionState) empty := new(SessionState)
if *s == *empty { if reflect.DeepEqual(*s, *empty) {
return errors.New("invalid empty session unmarshalled") return errors.New("invalid empty session unmarshalled")
} }

View File

@ -8,7 +8,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -186,6 +186,17 @@ func TestEncodeAndDecodeSessionState(t *testing.T) {
IDToken: "IDToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7", IDToken: "IDToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
ExpiresOn: &expires, ExpiresOn: &expires,
}, },
"With groups": {
Email: "username@example.com",
User: "username",
PreferredUsername: "preferred.username",
AccessToken: "AccessToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
IDToken: "IDToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
CreatedAt: &created,
ExpiresOn: &expires,
RefreshToken: "RefreshToken.12349871293847fdsaihf9238h4f91h8fr.1349f831y98fd7",
Groups: []string{"group-a", "group-b"},
},
} }
for _, secretSize := range []int{16, 24, 32} { for _, secretSize := range []int{16, 24, 32} {

View File

@ -3,7 +3,7 @@ package basic
import ( import (
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -9,7 +9,7 @@ import (
"io" "io"
"os" "os"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )

View File

@ -7,9 +7,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/util" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
) )
// MakeCookie constructs a cookie from the given parameters, // MakeCookie constructs a cookie from the given parameters,

View File

@ -2,8 +2,6 @@ package encryption
import ( import (
"crypto/hmac" "crypto/hmac"
// TODO (@NickMeves): Remove SHA1 signed cookie support in V7
"crypto/sha1" // #nosec G505
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
@ -95,16 +93,7 @@ func checkSignature(signature string, args ...string) bool {
if err != nil { if err != nil {
return false return false
} }
if checkHmac(signature, checkSig) { return checkHmac(signature, checkSig)
return true
}
// TODO (@NickMeves): Remove SHA1 signed cookie support in V7
legacySig, err := cookieSignature(sha1.New, args...)
if err != nil {
return false
}
return checkHmac(signature, legacySig)
} }
func checkHmac(input, expected string) bool { func checkHmac(input, expected string) bool {

View File

@ -94,8 +94,8 @@ func TestSignAndValidate(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, checkSignature(sha256sig, seed, key, value, epoch)) assert.True(t, checkSignature(sha256sig, seed, key, value, epoch))
// This should be switched to False after fully deprecating SHA1 // We don't validate legacy SHA1 signatures anymore
assert.True(t, checkSignature(sha1sig, seed, key, value, epoch)) assert.False(t, checkSignature(sha1sig, seed, key, value, epoch))
assert.False(t, checkSignature(sha256sig, seed, key, "tampered", epoch)) assert.False(t, checkSignature(sha256sig, seed, key, "tampered", epoch))
assert.False(t, checkSignature(sha1sig, seed, key, "tampered", epoch)) assert.False(t, checkSignature(sha1sig, seed, key, "tampered", epoch))

View File

@ -6,7 +6,7 @@ import (
"net/http" "net/http"
"strings" "strings"
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip" ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
) )
func GetRealClientIPParser(headerKey string) (ipapi.RealClientIPParser, error) { func GetRealClientIPParser(headerKey string) (ipapi.RealClientIPParser, error) {

View File

@ -6,7 +6,7 @@ import (
"reflect" "reflect"
"testing" "testing"
ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip" ipapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/ip"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -12,7 +12,7 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/util" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
) )
// AuthStatus defines the different types of auth logging that occur // AuthStatus defines the different types of auth logging that occur

View File

@ -5,9 +5,9 @@ import (
"net/http" "net/http"
"github.com/justinas/alice" "github.com/justinas/alice"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/authentication/basic" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/authentication/basic"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
func NewBasicAuthSessionLoader(validator basic.Validator) alice.Constructor { func NewBasicAuthSessionLoader(validator basic.Validator) alice.Constructor {

View File

@ -6,8 +6,8 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/middleware" middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"

View File

@ -8,9 +8,9 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/justinas/alice" "github.com/justinas/alice"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/middleware" middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
const jwtRegexFormat = `^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$` const jwtRegexFormat = `^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`

View File

@ -15,8 +15,8 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/middleware" middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"

View File

@ -4,7 +4,7 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -7,7 +7,7 @@ import (
"strings" "strings"
"github.com/justinas/alice" "github.com/justinas/alice"
"github.com/oauth2-proxy/oauth2-proxy/pkg/util" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
) )
const httpsScheme = "https" const httpsScheme = "https"

View File

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"github.com/justinas/alice" "github.com/justinas/alice"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/middleware" middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
) )
type scopeKey string type scopeKey string

View File

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/middleware" middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -8,8 +8,8 @@ import (
"time" "time"
"github.com/justinas/alice" "github.com/justinas/alice"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
// StoredSessionLoaderOptions cotnains all of the requirements to construct // StoredSessionLoaderOptions cotnains all of the requirements to construct

View File

@ -8,8 +8,8 @@ import (
"net/http/httptest" "net/http/httptest"
"time" "time"
middlewareapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/middleware" middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"

View File

@ -9,7 +9,7 @@ import (
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -8,11 +8,11 @@ import (
"strings" "strings"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
pkgcookies "github.com/oauth2-proxy/oauth2-proxy/pkg/cookies" pkgcookies "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
const ( const (

View File

@ -8,10 +8,10 @@ import (
"testing" "testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/tests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/tests"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"

View File

@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
) )
// Manager wraps a Store and handles the implementation details of the // Manager wraps a Store and handles the implementation details of the

View File

@ -3,9 +3,9 @@ package persistence
import ( import (
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/tests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/tests"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
) )

View File

@ -3,7 +3,7 @@ package persistence
import ( import (
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -13,10 +13,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/cookies" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
) )
// saveFunc performs a persistent store's save functionality using // saveFunc performs a persistent store's save functionality using

View File

@ -11,8 +11,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"

View File

@ -8,10 +8,10 @@ import (
"time" "time"
"github.com/go-redis/redis/v7" "github.com/go-redis/redis/v7"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/persistence" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/persistence"
) )
// SessionStore is an implementation of the persistence.Store // SessionStore is an implementation of the persistence.Store
@ -23,7 +23,7 @@ type SessionStore struct {
// NewRedisSessionStore initialises a new instance of the SessionStore and wraps // NewRedisSessionStore initialises a new instance of the SessionStore and wraps
// it in a persistence.Manager // it in a persistence.Manager
func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessions.SessionStore, error) { func NewRedisSessionStore(opts *options.SessionOptions, cookieOpts *options.Cookie) (sessions.SessionStore, error) {
client, err := newRedisClient(opts.Redis) client, err := NewRedisClient(opts.Redis)
if err != nil { if err != nil {
return nil, fmt.Errorf("error constructing redis client: %v", err) return nil, fmt.Errorf("error constructing redis client: %v", err)
} }
@ -64,9 +64,9 @@ func (store *SessionStore) Clear(ctx context.Context, key string) error {
return nil return nil
} }
// newRedisClient makes a redis.Client (either standalone, sentinel aware, or // NewRedisClient makes a redis.Client (either standalone, sentinel aware, or
// redis cluster) // redis cluster)
func newRedisClient(opts options.RedisStoreOptions) (Client, error) { func NewRedisClient(opts options.RedisStoreOptions) (Client, error) {
if opts.UseSentinel && opts.UseCluster { if opts.UseSentinel && opts.UseCluster {
return nil, fmt.Errorf("options redis-use-sentinel and redis-use-cluster are mutually exclusive") return nil, fmt.Errorf("options redis-use-sentinel and redis-use-cluster are mutually exclusive")
} }

View File

@ -9,11 +9,11 @@ import (
"github.com/Bose/minisentinel" "github.com/Bose/minisentinel"
"github.com/alicebob/miniredis/v2" "github.com/alicebob/miniredis/v2"
"github.com/go-redis/redis/v7" "github.com/go-redis/redis/v7"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/persistence" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/persistence"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/tests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/tests"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -3,10 +3,10 @@ package sessions
import ( import (
"fmt" "fmt"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/redis" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/redis"
) )
// NewSessionStore creates a SessionStore from the provided configuration // NewSessionStore creates a SessionStore from the provided configuration

View File

@ -6,12 +6,12 @@ import (
"testing" "testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions"
sessionscookie "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie" sessionscookie "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/cookie"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/persistence" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/persistence"
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/redis" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/redis"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -8,10 +8,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" sessionsapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
cookiesapi "github.com/oauth2-proxy/oauth2-proxy/pkg/cookies" cookiesapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/cookies"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -9,7 +9,7 @@ import (
"time" "time"
"github.com/mbland/hmacauth" "github.com/mbland/hmacauth"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/yhat/wsutil" "github.com/yhat/wsutil"
) )

View File

@ -13,7 +13,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"

View File

@ -6,8 +6,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
// ProxyErrorHandler is a function that will be used to render error pages when // ProxyErrorHandler is a function that will be used to render error pages when

View File

@ -8,7 +8,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"

View File

@ -11,7 +11,7 @@ import (
"path" "path"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"

View File

@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"sort" "sort"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
) )
func validateCookie(o options.Cookie) []string { func validateCookie(o options.Cookie) []string {

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -3,8 +3,8 @@ package validation
import ( import (
"os" "os"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"
) )

View File

@ -15,12 +15,12 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/mbland/hmacauth" "github.com/mbland/hmacauth"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/pkg/ip" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
"github.com/oauth2-proxy/oauth2-proxy/pkg/util" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
"github.com/oauth2-proxy/oauth2-proxy/providers" "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
) )
// Validate checks that required options are set and validates those that they // Validate checks that required options are set and validates those that they
@ -28,6 +28,7 @@ import (
func Validate(o *options.Options) error { func Validate(o *options.Options) error {
msgs := validateCookie(o.Cookie) msgs := validateCookie(o.Cookie)
msgs = append(msgs, validateSessionCookieMinimal(o)...) msgs = append(msgs, validateSessionCookieMinimal(o)...)
msgs = append(msgs, validateRedisSessionStore(o)...)
if o.SSLInsecureSkipVerify { if o.SSLInsecureSkipVerify {
// InsecureSkipVerify is a configurable option we allow // InsecureSkipVerify is a configurable option we allow
@ -152,6 +153,10 @@ func Validate(o *options.Options) error {
} }
if o.Scope == "" { if o.Scope == "" {
o.Scope = "openid email profile" o.Scope = "openid email profile"
if len(o.AllowedGroups) > 0 {
o.Scope += " groups"
}
} }
} }
@ -279,6 +284,7 @@ func parseProviderInfo(o *options.Options, msgs []string) []string {
case *providers.OIDCProvider: case *providers.OIDCProvider:
p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail p.AllowUnverifiedEmail = o.InsecureOIDCAllowUnverifiedEmail
p.UserIDClaim = o.UserIDClaim p.UserIDClaim = o.UserIDClaim
p.GroupsClaim = o.OIDCGroupsClaim
if o.GetOIDCVerifier() == nil { if o.GetOIDCVerifier() == nil {
msgs = append(msgs, "oidc provider requires an oidc issuer URL") msgs = append(msgs, "oidc provider requires an oidc issuer URL")
} else { } else {

View File

@ -10,7 +10,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -1,9 +1,13 @@
package validation package validation
import ( import (
"context"
"fmt"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/encryption"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/redis"
) )
func validateSessionCookieMinimal(o *options.Options) []string { func validateSessionCookieMinimal(o *options.Options) []string {
@ -30,3 +34,50 @@ func validateSessionCookieMinimal(o *options.Options) []string {
} }
return msgs return msgs
} }
// validateRedisSessionStore builds a Redis Client from the options and
// attempts to connect, Set, Get and Del a random health check key
func validateRedisSessionStore(o *options.Options) []string {
if o.Session.Type != options.RedisSessionStoreType {
return []string{}
}
client, err := redis.NewRedisClient(o.Session.Redis)
if err != nil {
return []string{fmt.Sprintf("unable to initialize a redis client: %v", err)}
}
nonce, err := encryption.Nonce()
if err != nil {
return []string{fmt.Sprintf("unable to generate a redis initialization test key: %v", err)}
}
key := fmt.Sprintf("%s-healthcheck-%s", o.Cookie.Name, nonce)
return sendRedisConnectionTest(client, key, nonce)
}
func sendRedisConnectionTest(client redis.Client, key string, val string) []string {
msgs := []string{}
ctx := context.Background()
err := client.Set(ctx, key, []byte(val), time.Duration(60)*time.Second)
if err != nil {
msgs = append(msgs, fmt.Sprintf("unable to set a redis initialization key: %v", err))
} else {
gval, err := client.Get(ctx, key)
if err != nil {
msgs = append(msgs,
fmt.Sprintf("unable to retrieve redis initialization key: %v", err))
}
if string(gval) != val {
msgs = append(msgs,
"the retrieved redis initialization key did not match the value we set")
}
}
err = client.Del(ctx, key)
if err != nil {
msgs = append(msgs, fmt.Sprintf("unable to delete the redis initialization key: %v", err))
}
return msgs
}

View File

@ -1,14 +1,17 @@
package validation package validation
import ( import (
"testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/Bose/minisentinel"
"github.com/alicebob/miniredis/v2"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
func Test_validateSessionCookieMinimal(t *testing.T) { var _ = Describe("Sessions", func() {
const ( const (
passAuthorizationMsg = "pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set" passAuthorizationMsg = "pass_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
setAuthorizationMsg = "set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set" setAuthorizationMsg = "set_authorization_header requires oauth tokens in sessions. session_cookie_minimal cannot be set"
@ -16,11 +19,16 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set" cookieRefreshMsg = "cookie_refresh > 0 requires oauth tokens in sessions. session_cookie_minimal cannot be set"
) )
testCases := map[string]struct { type cookieMinimalTableInput struct {
opts *options.Options opts *options.Options
errStrings []string errStrings []string
}{ }
"No minimal cookie session": {
DescribeTable("validateSessionCookieMinimal",
func(o *cookieMinimalTableInput) {
Expect(validateSessionCookieMinimal(o.opts)).To(ConsistOf(o.errStrings))
},
Entry("No minimal cookie session", &cookieMinimalTableInput{
opts: &options.Options{ opts: &options.Options{
Session: options.SessionOptions{ Session: options.SessionOptions{
Cookie: options.CookieStoreOptions{ Cookie: options.CookieStoreOptions{
@ -29,8 +37,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
}, },
}, },
errStrings: []string{}, errStrings: []string{},
}, }),
"No minimal cookie session & passAuthorization": { Entry("No minimal cookie session & passAuthorization", &cookieMinimalTableInput{
opts: &options.Options{ opts: &options.Options{
Session: options.SessionOptions{ Session: options.SessionOptions{
Cookie: options.CookieStoreOptions{ Cookie: options.CookieStoreOptions{
@ -40,8 +48,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
PassAuthorization: true, PassAuthorization: true,
}, },
errStrings: []string{}, errStrings: []string{},
}, }),
"Minimal cookie session no conflicts": { Entry("Minimal cookie session no conflicts", &cookieMinimalTableInput{
opts: &options.Options{ opts: &options.Options{
Session: options.SessionOptions{ Session: options.SessionOptions{
Cookie: options.CookieStoreOptions{ Cookie: options.CookieStoreOptions{
@ -50,8 +58,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
}, },
}, },
errStrings: []string{}, errStrings: []string{},
}, }),
"PassAuthorization conflict": { Entry("PassAuthorization conflict", &cookieMinimalTableInput{
opts: &options.Options{ opts: &options.Options{
Session: options.SessionOptions{ Session: options.SessionOptions{
Cookie: options.CookieStoreOptions{ Cookie: options.CookieStoreOptions{
@ -61,8 +69,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
PassAuthorization: true, PassAuthorization: true,
}, },
errStrings: []string{passAuthorizationMsg}, errStrings: []string{passAuthorizationMsg},
}, }),
"SetAuthorization conflict": { Entry("SetAuthorization conflict", &cookieMinimalTableInput{
opts: &options.Options{ opts: &options.Options{
Session: options.SessionOptions{ Session: options.SessionOptions{
Cookie: options.CookieStoreOptions{ Cookie: options.CookieStoreOptions{
@ -72,8 +80,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
SetAuthorization: true, SetAuthorization: true,
}, },
errStrings: []string{setAuthorizationMsg}, errStrings: []string{setAuthorizationMsg},
}, }),
"PassAccessToken conflict": { Entry("PassAccessToken conflict", &cookieMinimalTableInput{
opts: &options.Options{ opts: &options.Options{
Session: options.SessionOptions{ Session: options.SessionOptions{
Cookie: options.CookieStoreOptions{ Cookie: options.CookieStoreOptions{
@ -83,8 +91,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
PassAccessToken: true, PassAccessToken: true,
}, },
errStrings: []string{passAccessTokenMsg}, errStrings: []string{passAccessTokenMsg},
}, }),
"CookieRefresh conflict": { Entry("CookieRefresh conflict", &cookieMinimalTableInput{
opts: &options.Options{ opts: &options.Options{
Cookie: options.Cookie{ Cookie: options.Cookie{
Refresh: time.Hour, Refresh: time.Hour,
@ -96,8 +104,8 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
}, },
}, },
errStrings: []string{cookieRefreshMsg}, errStrings: []string{cookieRefreshMsg},
}, }),
"Multiple conflicts": { Entry("Multiple conflicts", &cookieMinimalTableInput{
opts: &options.Options{ opts: &options.Options{
Session: options.SessionOptions{ Session: options.SessionOptions{
Cookie: options.CookieStoreOptions{ Cookie: options.CookieStoreOptions{
@ -108,14 +116,228 @@ func Test_validateSessionCookieMinimal(t *testing.T) {
PassAccessToken: true, PassAccessToken: true,
}, },
errStrings: []string{passAuthorizationMsg, passAccessTokenMsg}, errStrings: []string{passAuthorizationMsg, passAccessTokenMsg},
}, }),
)
const (
clusterAndSentinelMsg = "unable to initialize a redis client: options redis-use-sentinel and redis-use-cluster are mutually exclusive"
parseWrongSchemeMsg = "unable to initialize a redis client: unable to parse redis url: invalid redis URL scheme: https"
parseWrongFormatMsg = "unable to initialize a redis client: unable to parse redis url: invalid redis database number: \"wrong\""
invalidPasswordSetMsg = "unable to set a redis initialization key: WRONGPASS invalid username-password pair"
invalidPasswordDelMsg = "unable to delete the redis initialization key: WRONGPASS invalid username-password pair"
unreachableRedisSetMsg = "unable to set a redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused"
unreachableRedisDelMsg = "unable to delete the redis initialization key: dial tcp 127.0.0.1:65535: connect: connection refused"
unreachableSentinelSetMsg = "unable to set a redis initialization key: redis: all sentinels are unreachable"
unrechableSentinelDelMsg = "unable to delete the redis initialization key: redis: all sentinels are unreachable"
)
type redisStoreTableInput struct {
// miniredis setup details
password string
useSentinel bool
setAddr bool
setSentinelAddr bool
setMasterName bool
opts *options.Options
errStrings []string
} }
for testName, tc := range testCases { DescribeTable("validateRedisSessionStore",
t.Run(testName, func(t *testing.T) { func(o *redisStoreTableInput) {
errStrings := validateSessionCookieMinimal(tc.opts) mr, err := miniredis.Run()
g := NewWithT(t) Expect(err).ToNot(HaveOccurred())
g.Expect(errStrings).To(ConsistOf(tc.errStrings)) mr.RequireAuth(o.password)
}) defer mr.Close()
}
} if o.setAddr && !o.useSentinel {
o.opts.Session.Redis.ConnectionURL = "redis://" + mr.Addr()
}
if o.useSentinel {
ms := minisentinel.NewSentinel(mr)
Expect(ms.Start()).To(Succeed())
defer ms.Close()
if o.setSentinelAddr {
o.opts.Session.Redis.SentinelConnectionURLs = []string{"redis://" + ms.Addr()}
}
if o.setMasterName {
o.opts.Session.Redis.SentinelMasterName = ms.MasterInfo().Name
}
}
Expect(validateRedisSessionStore(o.opts)).To(ConsistOf(o.errStrings))
},
Entry("cookie sessions are skipped", &redisStoreTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.CookieSessionStoreType,
},
},
errStrings: []string{},
}),
Entry("connect successfully to pure redis", &redisStoreTableInput{
setAddr: true,
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
},
},
errStrings: []string{},
}),
Entry("failed redis connection with wrong address", &redisStoreTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
ConnectionURL: "redis://127.0.0.1:65535",
},
},
},
errStrings: []string{unreachableRedisSetMsg, unreachableRedisDelMsg},
}),
Entry("fail to parse an invalid connection URL with wrong scheme", &redisStoreTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
ConnectionURL: "https://example.com",
},
},
},
errStrings: []string{parseWrongSchemeMsg},
}),
Entry("fail to parse an invalid connection URL with invalid format", &redisStoreTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
ConnectionURL: "redis://127.0.0.1:6379/wrong",
},
},
},
errStrings: []string{parseWrongFormatMsg},
}),
Entry("connect successfully to pure redis with password", &redisStoreTableInput{
password: "abcdef123",
setAddr: true,
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
Password: "abcdef123",
},
},
},
errStrings: []string{},
}),
Entry("failed connection with wrong password", &redisStoreTableInput{
password: "abcdef123",
setAddr: true,
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
Password: "zyxwtuv987",
},
},
},
errStrings: []string{invalidPasswordSetMsg, invalidPasswordDelMsg},
}),
Entry("connect successfully to sentinel redis", &redisStoreTableInput{
useSentinel: true,
setSentinelAddr: true,
setMasterName: true,
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
UseSentinel: true,
},
},
},
errStrings: []string{},
}),
Entry("connect successfully to sentinel redis with password", &redisStoreTableInput{
password: "abcdef123",
useSentinel: true,
setSentinelAddr: true,
setMasterName: true,
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
Password: "abcdef123",
UseSentinel: true,
},
},
},
errStrings: []string{},
}),
Entry("failed connection to sentinel redis with wrong password", &redisStoreTableInput{
password: "abcdef123",
useSentinel: true,
setSentinelAddr: true,
setMasterName: true,
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
Password: "zyxwtuv987",
UseSentinel: true,
},
},
},
errStrings: []string{invalidPasswordSetMsg, invalidPasswordDelMsg},
}),
Entry("failed connection to sentinel redis with wrong master name", &redisStoreTableInput{
useSentinel: true,
setSentinelAddr: true,
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
UseSentinel: true,
SentinelMasterName: "WRONG",
},
},
},
errStrings: []string{unreachableSentinelSetMsg, unrechableSentinelDelMsg},
}),
Entry("failed connection to sentinel redis with wrong address", &redisStoreTableInput{
useSentinel: true,
setMasterName: true,
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
UseSentinel: true,
SentinelConnectionURLs: []string{"redis://127.0.0.1:65535"},
},
},
},
errStrings: []string{unreachableSentinelSetMsg, unrechableSentinelDelMsg},
}),
Entry("sentinel and cluster both enabled fails", &redisStoreTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
UseSentinel: true,
UseCluster: true,
},
},
},
errStrings: []string{clusterAndSentinelMsg},
}),
)
})

View File

@ -5,7 +5,7 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
) )
func validateUpstreams(upstreams options.Upstreams) []string { func validateUpstreams(upstreams options.Upstreams) []string {

View File

@ -3,7 +3,7 @@ package validation
import ( import (
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"

View File

@ -3,7 +3,7 @@ package validation
import ( import (
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )

View File

@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
) )
var authorizedAccessToken = "imaginary_access_token" var authorizedAccessToken = "imaginary_access_token"

View File

@ -9,9 +9,9 @@ import (
"time" "time"
"github.com/bitly/go-simplejson" "github.com/bitly/go-simplejson"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
// AzureProvider represents an Azure based Identity Provider // AzureProvider represents an Azure based Identity Provider
@ -210,3 +210,12 @@ func (p *AzureProvider) GetEmailAddress(ctx context.Context, s *sessions.Session
return email, err return email, err
} }
func (p *AzureProvider) GetLoginURL(redirectURI, state string) string {
extraParams := url.Values{}
if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
extraParams.Add("resource", p.ProtectedResource.String())
}
a := makeLoginURL(p.ProviderData, redirectURI, state, extraParams)
return a.String()
}

View File

@ -213,3 +213,10 @@ func TestAzureProviderRedeemReturnsIdToken(t *testing.T) {
assert.Equal(t, timestamp, s.ExpiresOn.UTC()) assert.Equal(t, timestamp, s.ExpiresOn.UTC())
assert.Equal(t, "refresh1234", s.RefreshToken) assert.Equal(t, "refresh1234", s.RefreshToken)
} }
func TestAzureProviderProtectedResourceConfigured(t *testing.T) {
p := testAzureProvider("")
p.ProtectedResource, _ = url.Parse("http://my.resource.test")
result := p.GetLoginURL("https://my.test.app/oauth", "")
assert.Contains(t, result, "resource="+url.QueryEscape("http://my.resource.test"))
}

View File

@ -5,9 +5,9 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
// BitbucketProvider represents an Bitbucket based Identity Provider // BitbucketProvider represents an Bitbucket based Identity Provider

View File

@ -8,7 +8,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -5,8 +5,8 @@ import (
"errors" "errors"
"net/url" "net/url"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
// DigitalOceanProvider represents a DigitalOcean based Identity Provider // DigitalOceanProvider represents a DigitalOcean based Identity Provider

View File

@ -7,7 +7,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -5,8 +5,8 @@ import (
"errors" "errors"
"net/url" "net/url"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
// FacebookProvider represents an Facebook based Identity Provider // FacebookProvider represents an Facebook based Identity Provider

View File

@ -11,9 +11,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
// GitHubProvider represents an GitHub based Identity Provider // GitHubProvider represents an GitHub based Identity Provider

View File

@ -7,7 +7,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -7,8 +7,8 @@ import (
"time" "time"
oidc "github.com/coreos/go-oidc" oidc "github.com/coreos/go-oidc"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )

View File

@ -7,7 +7,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -13,9 +13,9 @@ import (
"strings" "strings"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
"golang.org/x/oauth2/google" "golang.org/x/oauth2/google"
admin "google.golang.org/api/admin/directory/v1" admin "google.golang.org/api/admin/directory/v1"
"google.golang.org/api/googleapi" "google.golang.org/api/googleapi"

View File

@ -5,8 +5,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
// stripToken is a helper function to obfuscate "access_token" // stripToken is a helper function to obfuscate "access_token"

View File

@ -8,7 +8,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -4,9 +4,9 @@ import (
"context" "context"
"net/url" "net/url"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
type KeycloakProvider struct { type KeycloakProvider struct {

View File

@ -7,7 +7,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -6,8 +6,8 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
// LinkedInProvider represents an LinkedIn based Identity Provider // LinkedInProvider represents an LinkedIn based Identity Provider

View File

@ -7,7 +7,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -11,8 +11,8 @@ import (
"time" "time"
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
) )
@ -225,20 +225,12 @@ func (p *LoginGovProvider) Redeem(ctx context.Context, redirectURL, code string)
// GetLoginURL overrides GetLoginURL to add login.gov parameters // GetLoginURL overrides GetLoginURL to add login.gov parameters
func (p *LoginGovProvider) GetLoginURL(redirectURI, state string) string { func (p *LoginGovProvider) GetLoginURL(redirectURI, state string) string {
a := *p.LoginURL extraParams := url.Values{}
params, _ := url.ParseQuery(a.RawQuery) if p.AcrValues == "" {
params.Set("redirect_uri", redirectURI) acr := "http://idmanagement.gov/ns/assurance/loa/1"
params.Set("approval_prompt", p.ApprovalPrompt) extraParams.Add("acr_values", acr)
params.Add("scope", p.Scope)
params.Set("client_id", p.ClientID)
params.Set("response_type", "code")
params.Add("state", state)
acr := p.AcrValues
if acr == "" {
acr = "http://idmanagement.gov/ns/assurance/loa/1"
} }
params.Add("acr_values", acr) extraParams.Add("nonce", p.Nonce)
params.Add("nonce", p.Nonce) a := makeLoginURL(p.ProviderData, redirectURI, state, extraParams)
a.RawQuery = params.Encode()
return a.String() return a.String()
} }

View File

@ -289,3 +289,10 @@ func TestLoginGovProviderBadNonce(t *testing.T) {
// The "badfakenonce" in the idtoken above should cause this to error out // The "badfakenonce" in the idtoken above should cause this to error out
assert.Error(t, err) assert.Error(t, err)
} }
func TestLoginGovProviderGetLoginURL(t *testing.T) {
p, _, _ := newLoginGovProvider()
result := p.GetLoginURL("http://redirect/", "")
assert.Contains(t, result, "acr_values="+url.QueryEscape("http://idmanagement.gov/ns/assurance/loa/1"))
assert.Contains(t, result, "nonce=fakenonce")
}

View File

@ -4,8 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
// NextcloudProvider represents an Nextcloud based Identity Provider // NextcloudProvider represents an Nextcloud based Identity Provider

View File

@ -7,7 +7,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -9,8 +9,8 @@ import (
oidc "github.com/coreos/go-oidc" oidc "github.com/coreos/go-oidc"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
const emailClaim = "email" const emailClaim = "email"
@ -22,6 +22,7 @@ type OIDCProvider struct {
Verifier *oidc.IDTokenVerifier Verifier *oidc.IDTokenVerifier
AllowUnverifiedEmail bool AllowUnverifiedEmail bool
UserIDClaim string UserIDClaim string
GroupsClaim string
} }
// NewOIDCProvider initiates a new OIDCProvider // NewOIDCProvider initiates a new OIDCProvider
@ -123,6 +124,7 @@ func (p *OIDCProvider) redeemRefreshToken(ctx context.Context, s *sessions.Sessi
s.IDToken = newSession.IDToken s.IDToken = newSession.IDToken
s.Email = newSession.Email s.Email = newSession.Email
s.User = newSession.User s.User = newSession.User
s.Groups = newSession.Groups
s.PreferredUsername = newSession.PreferredUsername s.PreferredUsername = newSession.PreferredUsername
} }
@ -204,6 +206,7 @@ func (p *OIDCProvider) createSessionStateInternal(ctx context.Context, idToken *
newSession.Email = claims.UserID // TODO Rename SessionState.Email to .UserID in the near future newSession.Email = claims.UserID // TODO Rename SessionState.Email to .UserID in the near future
newSession.User = claims.Subject newSession.User = claims.Subject
newSession.Groups = claims.Groups
newSession.PreferredUsername = claims.PreferredUsername newSession.PreferredUsername = claims.PreferredUsername
verifyEmail := (p.UserIDClaim == emailClaim) && !p.AllowUnverifiedEmail verifyEmail := (p.UserIDClaim == emailClaim) && !p.AllowUnverifiedEmail
@ -222,6 +225,7 @@ func (p *OIDCProvider) ValidateSessionState(ctx context.Context, s *sessions.Ses
func (p *OIDCProvider) findClaimsFromIDToken(ctx context.Context, idToken *oidc.IDToken, token *oauth2.Token) (*OIDCClaims, error) { func (p *OIDCProvider) findClaimsFromIDToken(ctx context.Context, idToken *oidc.IDToken, token *oauth2.Token) (*OIDCClaims, error) {
claims := &OIDCClaims{} claims := &OIDCClaims{}
// Extract default claims. // Extract default claims.
if err := idToken.Claims(&claims); err != nil { if err := idToken.Claims(&claims); err != nil {
return nil, fmt.Errorf("failed to parse default id_token claims: %v", err) return nil, fmt.Errorf("failed to parse default id_token claims: %v", err)
@ -236,6 +240,8 @@ func (p *OIDCProvider) findClaimsFromIDToken(ctx context.Context, idToken *oidc.
claims.UserID = fmt.Sprint(userID) claims.UserID = fmt.Sprint(userID)
} }
claims.Groups = p.extractGroupsFromRawClaims(claims.rawClaims)
// userID claim was not present or was empty in the ID Token // userID claim was not present or was empty in the ID Token
if claims.UserID == "" { if claims.UserID == "" {
// BearerToken case, allow empty UserID // BearerToken case, allow empty UserID
@ -273,10 +279,27 @@ func (p *OIDCProvider) findClaimsFromIDToken(ctx context.Context, idToken *oidc.
return claims, nil return claims, nil
} }
func (p *OIDCProvider) extractGroupsFromRawClaims(rawClaims map[string]interface{}) []string {
groups := []string{}
rawGroups, ok := rawClaims[p.GroupsClaim].([]interface{})
if rawGroups != nil && ok {
for _, rawGroup := range rawGroups {
group, ok := rawGroup.(string)
if ok {
groups = append(groups, group)
}
}
}
return groups
}
type OIDCClaims struct { type OIDCClaims struct {
rawClaims map[string]interface{} rawClaims map[string]interface{}
UserID string UserID string
Subject string `json:"sub"` Subject string `json:"sub"`
Verified *bool `json:"email_verified"` Verified *bool `json:"email_verified"`
PreferredUsername string `json:"preferred_username"` PreferredUsername string `json:"preferred_username"`
Groups []string
} }

View File

@ -20,7 +20,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
) )
const accessToken = "access_token" const accessToken = "access_token"
@ -29,10 +29,12 @@ const clientID = "https://test.myapp.com"
const secret = "secret" const secret = "secret"
type idTokenClaims struct { type idTokenClaims struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
Phone string `json:"phone_number,omitempty"` Phone string `json:"phone_number,omitempty"`
Picture string `json:"picture,omitempty"` Picture string `json:"picture,omitempty"`
Groups []string `json:"groups,omitempty"`
OtherGroups []string `json:"other_groups,omitempty"`
jwt.StandardClaims jwt.StandardClaims
} }
@ -49,6 +51,8 @@ var defaultIDToken idTokenClaims = idTokenClaims{
"janed@me.com", "janed@me.com",
"+4798765432", "+4798765432",
"http://mugbook.com/janed/me.jpg", "http://mugbook.com/janed/me.jpg",
[]string{"test:a", "test:b"},
[]string{"test:c", "test:d"},
jwt.StandardClaims{ jwt.StandardClaims{
Audience: "https://test.myapp.com", Audience: "https://test.myapp.com",
ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(), ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
@ -65,6 +69,8 @@ var minimalIDToken idTokenClaims = idTokenClaims{
"", "",
"", "",
"", "",
[]string{},
[]string{},
jwt.StandardClaims{ jwt.StandardClaims{
Audience: "https://test.myapp.com", Audience: "https://test.myapp.com",
ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(), ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
@ -273,25 +279,39 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) {
const profileURLEmail = "janed@me.com" const profileURLEmail = "janed@me.com"
testCases := map[string]struct { testCases := map[string]struct {
IDToken idTokenClaims IDToken idTokenClaims
ExpectedUser string GroupsClaim string
ExpectedEmail string ExpectedUser string
ExpectedEmail string
ExpectedGroups []string
}{ }{
"Default IDToken": { "Default IDToken": {
IDToken: defaultIDToken, IDToken: defaultIDToken,
ExpectedUser: defaultIDToken.Subject, GroupsClaim: "groups",
ExpectedEmail: defaultIDToken.Email, ExpectedUser: defaultIDToken.Subject,
ExpectedEmail: defaultIDToken.Email,
ExpectedGroups: []string{"test:a", "test:b"},
}, },
"Minimal IDToken with no email claim": { "Minimal IDToken with no email claim": {
IDToken: minimalIDToken, IDToken: minimalIDToken,
ExpectedUser: minimalIDToken.Subject, GroupsClaim: "groups",
ExpectedEmail: minimalIDToken.Subject, ExpectedUser: minimalIDToken.Subject,
ExpectedEmail: minimalIDToken.Subject,
ExpectedGroups: []string{},
},
"Custom Groups Claim": {
IDToken: defaultIDToken,
GroupsClaim: "other_groups",
ExpectedUser: defaultIDToken.Subject,
ExpectedEmail: defaultIDToken.Email,
ExpectedGroups: []string{"test:c", "test:d"},
}, },
} }
for testName, tc := range testCases { for testName, tc := range testCases {
t.Run(testName, func(t *testing.T) { t.Run(testName, func(t *testing.T) {
jsonResp := []byte(fmt.Sprintf(`{"email":"%s"}`, profileURLEmail)) jsonResp := []byte(fmt.Sprintf(`{"email":"%s"}`, profileURLEmail))
server, provider := newTestSetup(jsonResp) server, provider := newTestSetup(jsonResp)
provider.GroupsClaim = tc.GroupsClaim
defer server.Close() defer server.Close()
rawIDToken, err := newSignedTestIDToken(tc.IDToken) rawIDToken, err := newSignedTestIDToken(tc.IDToken)
@ -311,6 +331,7 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) {
assert.Equal(t, tc.ExpectedEmail, ss.Email) assert.Equal(t, tc.ExpectedEmail, ss.Email)
assert.Equal(t, rawIDToken, ss.IDToken) assert.Equal(t, rawIDToken, ss.IDToken)
assert.Equal(t, rawIDToken, ss.AccessToken) assert.Equal(t, rawIDToken, ss.AccessToken)
assert.Equal(t, tc.ExpectedGroups, ss.Groups)
assert.Equal(t, "", ss.RefreshToken) assert.Equal(t, "", ss.RefreshToken)
}) })
} }

View File

@ -5,7 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
// ProviderData contains information required to configure all implementations // ProviderData contains information required to configure all implementations

View File

@ -10,8 +10,8 @@ import (
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/requests" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
) )
var _ Provider = (*ProviderData)(nil) var _ Provider = (*ProviderData)(nil)
@ -75,22 +75,8 @@ func (p *ProviderData) Redeem(ctx context.Context, redirectURL, code string) (s
// GetLoginURL with typical oauth parameters // GetLoginURL with typical oauth parameters
func (p *ProviderData) GetLoginURL(redirectURI, state string) string { func (p *ProviderData) GetLoginURL(redirectURI, state string) string {
a := *p.LoginURL extraParams := url.Values{}
params, _ := url.ParseQuery(a.RawQuery) a := makeLoginURL(p, redirectURI, state, extraParams)
params.Set("redirect_uri", redirectURI)
if p.AcrValues != "" {
params.Add("acr_values", p.AcrValues)
}
if p.Prompt != "" {
params.Set("prompt", p.Prompt)
} else { // Legacy variant of the prompt param:
params.Set("approval_prompt", p.ApprovalPrompt)
}
params.Add("scope", p.Scope)
params.Set("client_id", p.ClientID)
params.Set("response_type", "code")
params.Add("state", state)
a.RawQuery = params.Encode()
return a.String() return a.String()
} }
@ -104,11 +90,6 @@ func (p *ProviderData) GetUserName(ctx context.Context, s *sessions.SessionState
return "", errors.New("not implemented") return "", errors.New("not implemented")
} }
// GetPreferredUsername returns the Account preferred username
func (p *ProviderData) GetPreferredUsername(ctx context.Context, s *sessions.SessionState) (string, error) {
return "", errors.New("not implemented")
}
// ValidateGroup validates that the provided email exists in the configured provider // ValidateGroup validates that the provided email exists in the configured provider
// email group(s). // email group(s).
func (p *ProviderData) ValidateGroup(email string) bool { func (p *ProviderData) ValidateGroup(email string) bool {

View File

@ -6,7 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"github.com/coreos/go-oidc" "github.com/coreos/go-oidc"
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
) )
// Provider represents an upstream identity provider implementation // Provider represents an upstream identity provider implementation
@ -12,7 +12,6 @@ type Provider interface {
Data() *ProviderData Data() *ProviderData
GetEmailAddress(ctx context.Context, s *sessions.SessionState) (string, error) GetEmailAddress(ctx context.Context, s *sessions.SessionState) (string, error)
GetUserName(ctx context.Context, s *sessions.SessionState) (string, error) GetUserName(ctx context.Context, s *sessions.SessionState) (string, error)
GetPreferredUsername(ctx context.Context, s *sessions.SessionState) (string, error)
Redeem(ctx context.Context, redirectURI, code string) (*sessions.SessionState, error) Redeem(ctx context.Context, redirectURI, code string) (*sessions.SessionState, error)
ValidateGroup(string) bool ValidateGroup(string) bool
ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool ValidateSessionState(ctx context.Context, s *sessions.SessionState) bool

View File

@ -3,6 +3,7 @@ package providers
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
) )
const ( const (
@ -29,3 +30,28 @@ func makeOIDCHeader(accessToken string) http.Header {
} }
return makeAuthorizationHeader(tokenTypeBearer, accessToken, extraHeaders) return makeAuthorizationHeader(tokenTypeBearer, accessToken, extraHeaders)
} }
func makeLoginURL(p *ProviderData, redirectURI, state string, extraParams url.Values) url.URL {
a := *p.LoginURL
params, _ := url.ParseQuery(a.RawQuery)
params.Set("redirect_uri", redirectURI)
if p.AcrValues != "" {
params.Add("acr_values", p.AcrValues)
}
if p.Prompt != "" {
params.Set("prompt", p.Prompt)
} else { // Legacy variant of the prompt param:
params.Set("approval_prompt", p.ApprovalPrompt)
}
params.Add("scope", p.Scope)
params.Set("client_id", p.ClientID)
params.Set("response_type", "code")
params.Add("state", state)
for n, p := range extraParams {
for _, v := range p {
params.Add(n, v)
}
}
a.RawQuery = params.Encode()
return a
}

View File

@ -5,7 +5,7 @@ import (
"path" "path"
"strings" "strings"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
func loadTemplates(dir string) *template.Template { func loadTemplates(dir string) *template.Template {

View File

@ -9,7 +9,7 @@ import (
"sync/atomic" "sync/atomic"
"unsafe" "unsafe"
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
) )
// UserMap holds information from the authenticated emails file // UserMap holds information from the authenticated emails file

Some files were not shown because too many files have changed in this diff Show More