Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for custom OIDC prompts #3409

Merged
merged 1 commit into from
Mar 7, 2020
Merged
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
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