Skip to content

Commit

Permalink
Adds support for custom OIDC prompts (#3409)
Browse files Browse the repository at this point in the history
This commit adds support for custom OIDC prompt values.

Read about possible prompt values here:

https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest

Three cases are possible:

* Prompt value is not set, this defaults to
OIDC prompt value to select_account value to preserve backwards
compatibility.

```yaml
kind: oidc
version: v2
metadata:
  name: connector
spec:
  prompt: 'login consent'
```

* Prompt value is set to empty string, it will be omitted
from the auth request.

```yaml
kind: oidc
version: v2
metadata:
  name: connector
spec:
  prompt: ''
```

* Prompt value is set to non empty string, it will be included
in the auth request as is.

```yaml
kind: oidc
version: v2
metadata:
  name: connector
spec:
  prompt: 'login consent'
```

Tested with Auth0 OIDC connector on teleport 4.2 enterprise.
  • Loading branch information
klizhentas authored and russjones committed Mar 21, 2020
1 parent 235a146 commit 924dd8f
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 5 deletions.
12 changes: 12 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,18 @@ const (
DebugLevel = "debug"
)

const (
// These values are from https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest

// OIDCPromptSelectAccount instructs the Authorization Server to
// prompt the End-User to select a user account.
OIDCPromptSelectAccount = "select_account"

// OIDCAccessTypeOnline indicates that OIDC flow should be performed
// with Authorization server and user connected online
OIDCAccessTypeOnline = "online"
)

// Component generates "component:subcomponent1:subcomponent2" strings used
// in debugging
func Component(components ...string) string {
Expand Down
5 changes: 3 additions & 2 deletions lib/auth/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,9 @@ func (s *AuthServer) CreateOIDCAuthRequest(req services.OIDCAuthRequest) (*servi
}

req.StateToken = stateToken
// online is OIDC online scope, "select_account" forces user to always select account
req.RedirectURL = oauthClient.AuthCodeURL(req.StateToken, "online", "select_account")

// online indicates that this login should only work online
req.RedirectURL = oauthClient.AuthCodeURL(req.StateToken, teleport.OIDCAccessTypeOnline, connector.GetPrompt())

// if the connector has an Authentication Context Class Reference (ACR) value set,
// update redirect url and add it as a query value.
Expand Down
32 changes: 30 additions & 2 deletions lib/services/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ type OIDCConnector interface {
SetIssuerURL(string)
// SetRedirectURL sets RedirectURL
SetRedirectURL(string)
// SetPrompt sets OIDC prompt value
SetPrompt(string)
// GetPrompt returns OIDC prompt value,
GetPrompt() string
// SetACR sets the Authentication Context Class Reference (ACR) value.
SetACR(string)
// SetProvider sets the identity provider.
Expand Down Expand Up @@ -239,7 +243,23 @@ type OIDCConnectorV2 struct {
Spec OIDCConnectorSpecV2 `json:"spec"`
}

// GetGoogleServiceAccountFile returns an optional path to google service account file
// SetPrompt sets OIDC prompt value
func (o *OIDCConnectorV2) SetPrompt(p string) {
o.Spec.Prompt = &p
}

// GetPrompt returns OIDC prompt value,
// * if not set, in this case defaults to select_account for backwards compatibility
// * set to empty string, in this case it will be omitted
// * and any non empty value, passed as is
func (o *OIDCConnectorV2) GetPrompt() string {
if o.Spec.Prompt == nil {
return teleport.OIDCPromptSelectAccount
}
return *o.Spec.Prompt
}

// GetGoogleServiceAccountURI returns an optional path to google service account file
func (o *OIDCConnectorV2) GetGoogleServiceAccountURI() string {
return o.Spec.GoogleServiceAccountURI
}
Expand Down Expand Up @@ -586,7 +606,10 @@ const OIDCConnectorV2SchemaTemplate = `{
}`

// OIDCConnectorSpecV2 specifies configuration for Open ID Connect compatible external
// identity provider, e.g. google in some organisation
// identity provider:
//
// https://openid.net/specs/openid-connect-core-1_0.html
//
type OIDCConnectorSpecV2 struct {
// Issuer URL is the endpoint of the provider, e.g. https://accounts.google.com
IssuerURL string `json:"issuer_url"`
Expand All @@ -608,6 +631,10 @@ type OIDCConnectorSpecV2 struct {
Display string `json:"display,omitempty"`
// Scope is additional scopes set by provder
Scope []string `json:"scope,omitempty"`
// Prompt is optional OIDC prompt, empty string omits prompt
// if not specified, defaults to select_account for backwards compatibility
// otherwise, is set to a value specified in this field
Prompt *string `json:"prompt,omitempty"`
// ClaimsToRoles specifies dynamic mapping from claims to roles
ClaimsToRoles []ClaimMapping `json:"claims_to_roles,omitempty"`
// GoogleServiceAccountURI is a path to google service account uri
Expand All @@ -629,6 +656,7 @@ var OIDCConnectorSpecV2Schema = fmt.Sprintf(`{
"acr_values": {"type": "string"},
"provider": {"type": "string"},
"display": {"type": "string"},
"prompt": {"type": "string"},
"google_service_account_uri": {"type": "string"},
"google_admin_email": {"type": "string"},
"scope": {
Expand Down
69 changes: 68 additions & 1 deletion lib/services/oidc_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
Copyright 2017 Gravitational, Inc.
Copyright 2017-2020 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -18,6 +18,7 @@ package services
import (
"fmt"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/utils"

"gopkg.in/check.v1"
Expand Down Expand Up @@ -79,6 +80,72 @@ func (s *OIDCSuite) TestUnmarshal(c *check.C) {
c.Assert(oc.GetClientID(), check.Equals, "id-from-google.apps.googleusercontent.com")
c.Assert(oc.GetRedirectURL(), check.Equals, "https://localhost:3080/v1/webapi/oidc/callback")
c.Assert(oc.GetDisplay(), check.Equals, "whatever")
c.Assert(oc.GetPrompt(), check.Equals, teleport.OIDCPromptSelectAccount)
}

// TestUnmarshalEmptyPrompt makes sure that empty prompt value
// that is set does not default to select_account
func (s *OIDCSuite) TestUnmarshalEmptyPrompt(c *check.C) {
input := `
{
"kind": "oidc",
"version": "v2",
"metadata": {
"name": "google"
},
"spec": {
"issuer_url": "https://accounts.google.com",
"client_id": "id-from-google.apps.googleusercontent.com",
"client_secret": "secret-key-from-google",
"redirect_url": "https://localhost:3080/v1/webapi/oidc/callback",
"display": "whatever",
"scope": ["roles"],
"prompt": ""
}
}
`

oc, err := GetOIDCConnectorMarshaler().UnmarshalOIDCConnector([]byte(input))
c.Assert(err, check.IsNil)

c.Assert(oc.GetName(), check.Equals, "google")
c.Assert(oc.GetIssuerURL(), check.Equals, "https://accounts.google.com")
c.Assert(oc.GetClientID(), check.Equals, "id-from-google.apps.googleusercontent.com")
c.Assert(oc.GetRedirectURL(), check.Equals, "https://localhost:3080/v1/webapi/oidc/callback")
c.Assert(oc.GetDisplay(), check.Equals, "whatever")
c.Assert(oc.GetPrompt(), check.Equals, "")
}

// TestUnmarshalPromptValue makes sure that prompt value is set properly
func (s *OIDCSuite) TestUnmarshalPromptValue(c *check.C) {
input := `
{
"kind": "oidc",
"version": "v2",
"metadata": {
"name": "google"
},
"spec": {
"issuer_url": "https://accounts.google.com",
"client_id": "id-from-google.apps.googleusercontent.com",
"client_secret": "secret-key-from-google",
"redirect_url": "https://localhost:3080/v1/webapi/oidc/callback",
"display": "whatever",
"scope": ["roles"],
"prompt": "consent login"
}
}
`

oc, err := GetOIDCConnectorMarshaler().UnmarshalOIDCConnector([]byte(input))
c.Assert(err, check.IsNil)

c.Assert(oc.GetName(), check.Equals, "google")
c.Assert(oc.GetIssuerURL(), check.Equals, "https://accounts.google.com")
c.Assert(oc.GetClientID(), check.Equals, "id-from-google.apps.googleusercontent.com")
c.Assert(oc.GetRedirectURL(), check.Equals, "https://localhost:3080/v1/webapi/oidc/callback")
c.Assert(oc.GetDisplay(), check.Equals, "whatever")
c.Assert(oc.GetPrompt(), check.Equals, "consent login")
}

func (s *OIDCSuite) TestUnmarshalInvalid(c *check.C) {
Expand Down

0 comments on commit 924dd8f

Please sign in to comment.