Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
run: docker-compose -f docker-compose.test.yaml up -d

- name: Create kind cluster
uses: helm/kind-action@v1.1.0
uses: helm/kind-action@v1.2.0
with:
version: v0.11.1
node_image: kindest/node:v1.19.11@sha256:07db187ae84b4b7de440a73886f008cf903fcf5764ba8106a9fd5243d6f32729
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Calculate Docker image tags
id: tags
Expand Down Expand Up @@ -49,30 +49,30 @@ jobs:
platforms: all

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
with:
install: true
version: latest
# TODO: Remove driver-opts once fix is released docker/buildx#386
driver-opts: image=moby/buildkit:master

- name: Login to GitHub Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ github.token }}
if: github.event_name == 'push'

- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
if: github.event_name == 'push'

- name: Build and push
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm/v7,linux/arm64
Expand Down
2 changes: 1 addition & 1 deletion connector/authproxy/authproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type callback struct {
}

// LoginURL returns the URL to redirect the user to login with.
func (m *callback) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) {
func (m *callback) LoginURL(s connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
u, err := url.Parse(callbackURL)
if err != nil {
return "", fmt.Errorf("failed to parse callbackURL %q: %v", callbackURL, err)
Expand Down
3 changes: 2 additions & 1 deletion connector/bitbucketcloud/bitbucketcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"sync"
"time"

Expand Down Expand Up @@ -111,7 +112,7 @@ func (b *bitbucketConnector) oauth2Config(scopes connector.Scopes) *oauth2.Confi
}
}

func (b *bitbucketConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
func (b *bitbucketConnector) LoginURL(scopes connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
if b.redirectURI != callbackURL {
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, b.redirectURI)
}
Expand Down
3 changes: 2 additions & 1 deletion connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package connector
import (
"context"
"net/http"
"net/url"
)

// Connector is a mechanism for federating login to a remote identity service.
Expand Down Expand Up @@ -63,7 +64,7 @@ type CallbackConnector interface {
// requested if one has already been issues. There's no good general answer
// for these kind of restrictions, and may require this package to become more
// aware of the global set of user/connector interactions.
LoginURL(s Scopes, callbackURL, state string) (string, error)
LoginURL(s Scopes, callbackURL, state string, forwardedParams url.Values) (string, error)

// Handle the callback to the server and return an identity.
HandleCallback(s Scopes, r *http.Request) (identity Identity, err error)
Expand Down
3 changes: 2 additions & 1 deletion connector/gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"sync"
"time"
Expand Down Expand Up @@ -82,7 +83,7 @@ func (c *giteaConnector) oauth2Config(_ connector.Scopes) *oauth2.Config {
}
}

func (c *giteaConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
func (c *giteaConnector) LoginURL(scopes connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
if c.redirectURI != callbackURL {
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", c.redirectURI, callbackURL)
}
Expand Down
3 changes: 2 additions & 1 deletion connector/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"io/ioutil"
"net"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
Expand Down Expand Up @@ -187,7 +188,7 @@ func (c *githubConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config {
}
}

func (c *githubConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
func (c *githubConnector) LoginURL(scopes connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
if c.redirectURI != callbackURL {
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
}
Expand Down
3 changes: 2 additions & 1 deletion connector/gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"

"golang.org/x/oauth2"
Expand Down Expand Up @@ -98,7 +99,7 @@ func (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config {
}
}

func (c *gitlabConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
func (c *gitlabConnector) LoginURL(scopes connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
if c.redirectURI != callbackURL {
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", c.redirectURI, callbackURL)
}
Expand Down
3 changes: 2 additions & 1 deletion connector/google/google.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"

"github.com/coreos/go-oidc/v3/oidc"
Expand Down Expand Up @@ -120,7 +121,7 @@ func (c *googleConnector) Close() error {
return nil
}

func (c *googleConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) {
func (c *googleConnector) LoginURL(s connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
if c.redirectURI != callbackURL {
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
}
Expand Down
3 changes: 2 additions & 1 deletion connector/linkedin/linkedin.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"

"golang.org/x/oauth2"
Expand Down Expand Up @@ -62,7 +63,7 @@ var (
)

// LoginURL returns an access token request URL
func (c *linkedInConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
func (c *linkedInConnector) LoginURL(scopes connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
if c.oauth2Config.RedirectURL != callbackURL {
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q",
callbackURL, c.oauth2Config.RedirectURL)
Expand Down
3 changes: 2 additions & 1 deletion connector/microsoft/microsoft.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -151,7 +152,7 @@ func (c *microsoftConnector) oauth2Config(scopes connector.Scopes) *oauth2.Confi
}
}

func (c *microsoftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
func (c *microsoftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
if c.redirectURI != callbackURL {
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
}
Expand Down
2 changes: 1 addition & 1 deletion connector/mock/connectortest.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type Callback struct {
}

// LoginURL returns the URL to redirect the user to login with.
func (m *Callback) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) {
func (m *Callback) LoginURL(s connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
u, err := url.Parse(callbackURL)
if err != nil {
return "", fmt.Errorf("failed to parse callbackURL %q: %v", callbackURL, err)
Expand Down
86 changes: 54 additions & 32 deletions connector/oidc/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ type Config struct {
// InsecureEnableGroups enables groups claims. This is disabled by default until https://github.com/dexidp/dex/issues/1065 is resolved
InsecureEnableGroups bool `json:"insecureEnableGroups"`

// Skips checking whether the requested domain in the Login Callback matches the configured Issuer
InsecureSkipIssuerCallbackDomainCheck bool `json:"insecureSkipIssuerCallbackDomainCheck"`

// GetUserInfo uses the userinfo endpoint to get additional claims for
// the token. This is especially useful where upstreams return "thin"
// id tokens
Expand All @@ -56,6 +59,8 @@ type Config struct {
// PromptType will be used fot the prompt parameter (when offline_access, by default prompt=consent)
PromptType string `json:"promptType"`

ForwardedLoginParams []string `json:"forwardedLoginParams"`

ClaimMapping struct {
// Configurable key which contains the preferred username claims
PreferredUsernameKey string `json:"preferred_username"` // defaults to "preferred_username"
Expand Down Expand Up @@ -125,6 +130,8 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
scopes = append(scopes, "profile", "email")
}

scopes = append(scopes, "offline_access")

// PromptType should be "consent" by default, if not set
if c.PromptType == "" {
c.PromptType = "consent"
Expand All @@ -144,18 +151,20 @@ func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, e
verifier: provider.Verifier(
&oidc.Config{ClientID: clientID},
),
logger: logger,
cancel: cancel,
hostedDomains: c.HostedDomains,
insecureSkipEmailVerified: c.InsecureSkipEmailVerified,
insecureEnableGroups: c.InsecureEnableGroups,
getUserInfo: c.GetUserInfo,
promptType: c.PromptType,
userIDKey: c.UserIDKey,
userNameKey: c.UserNameKey,
preferredUsernameKey: c.ClaimMapping.PreferredUsernameKey,
emailKey: c.ClaimMapping.EmailKey,
groupsKey: c.ClaimMapping.GroupsKey,
logger: logger,
cancel: cancel,
hostedDomains: c.HostedDomains,
insecureSkipEmailVerified: c.InsecureSkipEmailVerified,
insecureEnableGroups: c.InsecureEnableGroups,
insecureSkipIssuerCallbackDomainCheck: c.InsecureSkipIssuerCallbackDomainCheck,
getUserInfo: c.GetUserInfo,
promptType: c.PromptType,
userIDKey: c.UserIDKey,
userNameKey: c.UserNameKey,
preferredUsernameKey: c.ClaimMapping.PreferredUsernameKey,
emailKey: c.ClaimMapping.EmailKey,
groupsKey: c.ClaimMapping.GroupsKey,
forwardedLoginParams: c.ForwardedLoginParams,
}, nil
}

Expand All @@ -165,31 +174,33 @@ var (
)

type oidcConnector struct {
provider *oidc.Provider
redirectURI string
oauth2Config *oauth2.Config
verifier *oidc.IDTokenVerifier
cancel context.CancelFunc
logger log.Logger
hostedDomains []string
insecureSkipEmailVerified bool
insecureEnableGroups bool
getUserInfo bool
promptType string
userIDKey string
userNameKey string
preferredUsernameKey string
emailKey string
groupsKey string
provider *oidc.Provider
redirectURI string
oauth2Config *oauth2.Config
verifier *oidc.IDTokenVerifier
cancel context.CancelFunc
logger log.Logger
hostedDomains []string
insecureSkipEmailVerified bool
insecureEnableGroups bool
insecureSkipIssuerCallbackDomainCheck bool
getUserInfo bool
promptType string
userIDKey string
userNameKey string
preferredUsernameKey string
emailKey string
groupsKey string
forwardedLoginParams []string
}

func (c *oidcConnector) Close() error {
c.cancel()
return nil
}

func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) {
if c.redirectURI != callbackURL {
func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string, forwardParams url.Values) (string, error) {
if c.redirectURI != callbackURL && !c.insecureSkipIssuerCallbackDomainCheck {
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
}

Expand All @@ -202,8 +213,19 @@ func (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string)
opts = append(opts, oauth2.SetAuthURLParam("hd", preferredDomain))
}

if s.OfflineAccess {
opts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", c.promptType))
for p := range forwardParams {
paramApproved := false
for _, approvedParam := range c.forwardedLoginParams {
if p == approvedParam {
paramApproved = true
break
}
}
if paramApproved {
opts = append(opts, oauth2.SetAuthURLParam(p, forwardParams.Get(p)))
} else {
c.logger.Infof("oidc: auth query parameter %s, is unapproved and was not forwarded to the connector idp", p)
}
}
return c.oauth2Config.AuthCodeURL(state, opts...), nil
}
Expand Down
3 changes: 2 additions & 1 deletion connector/openshift/openshift.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"time"

Expand Down Expand Up @@ -117,7 +118,7 @@ func (c *openshiftConnector) Close() error {
}

// LoginURL returns the URL to redirect the user to login with.
func (c *openshiftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
func (c *openshiftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string, _ url.Values) (string, error) {
if c.redirectURI != callbackURL {
return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
}
Expand Down
4 changes: 2 additions & 2 deletions server/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
}

func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
authReq, err := s.parseAuthorizationRequest(r)
authReq, forwardParams, err := s.parseAuthorizationRequest(r)
if err != nil {
s.logger.Errorf("Failed to parse authorization request: %v", err)
status := http.StatusInternalServerError
Expand Down Expand Up @@ -250,7 +250,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
// Use the auth request ID as the "state" token.
//
// TODO(ericchiang): Is this appropriate or should we also be using a nonce?
callbackURL, err := conn.LoginURL(scopes, s.absURL("/callback"), authReq.ID)
callbackURL, err := conn.LoginURL(scopes, s.absURL("/callback"), authReq.ID, forwardParams)
if err != nil {
s.logger.Errorf("Connector %q returned error when creating callback: %v", connID, err)
s.renderError(r, w, http.StatusInternalServerError, "Login error.")
Expand Down
Loading