Skip to content

Commit

Permalink
Merge pull request #216 from buzzfeed/update-google-version-endpoints
Browse files Browse the repository at this point in the history
auth: update google version endpoints
  • Loading branch information
Justin Hines committed Jun 24, 2019
2 parents 5904bbf + 14f2a96 commit 568049a
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 70 deletions.
6 changes: 3 additions & 3 deletions internal/auth/authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1608,9 +1608,9 @@ func TestGoogleProviderApiSettings(t *testing.T) {
t.Fatalf("unexpected err generating google provider: %v", err)
}
p := provider.Data()
testutil.Equal(t, "https://accounts.google.com/o/oauth2/auth?access_type=offline",
testutil.Equal(t, "https://accounts.google.com/o/oauth2/v2/auth",
p.SignInURL.String())
testutil.Equal(t, "https://www.googleapis.com/oauth2/v3/token",
testutil.Equal(t, "https://www.googleapis.com/oauth2/v4/token",
p.RedeemURL.String())

testutil.Equal(t, "", p.ProfileURL.String())
Expand All @@ -1630,5 +1630,5 @@ func TestGoogleGroupInvalidFile(t *testing.T) {
},
)
testutil.NotEqual(t, nil, err)
testutil.Equal(t, "invalid Google credentials file: file_doesnt_exist.json", err.Error())
testutil.Equal(t, "could not read google credentials file", err.Error())
}
3 changes: 3 additions & 0 deletions internal/auth/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import (
//
// PROVIDER_*_GOOGLE_CREDENTIALS
// PROVIDER_*_GOOGLE_IMPERSONATE
// PROVIDER_*_GOOGLE_PROMPT
// PROVIDER_*_GOOGLE_DOMAIN
//
// PROVIDER_*_OKTA_URL
// PROVIDER_*_OKTA_SERVER
Expand Down Expand Up @@ -227,6 +229,7 @@ type GoogleProviderConfig struct {
Credentials string `mapstructure:"credentials"`
Impersonate string `mapstructure:"impersonate"`
ApprovalPrompt string `mapstructure:"prompt"`
HostedDomain string `mapstructure:"domain"`
}

func (gpc GoogleProviderConfig) Validate() error {
Expand Down
18 changes: 6 additions & 12 deletions internal/auth/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/base64"
"fmt"
"net/url"
"os"
"path"

"github.com/buzzfeed/sso/internal/auth/providers"
Expand All @@ -28,16 +27,12 @@ func newProvider(pc ProviderConfig, sc SessionConfig) (providers.Provider, error
switch pc.ProviderType {
case providers.GoogleProviderName: // Google
gpc := pc.GoogleProviderConfig
p.ApprovalPrompt = gpc.ApprovalPrompt

if gpc.Credentials != "" {
_, err := os.Open(gpc.Credentials)
if err != nil {
return nil, fmt.Errorf("invalid Google credentials file: %s", gpc.Credentials)
}
}

googleProvider, err := providers.NewGoogleProvider(p, gpc.Impersonate, gpc.Credentials)
googleProvider, err := providers.NewGoogleProvider(p,
gpc.ApprovalPrompt,
gpc.HostedDomain,
gpc.Impersonate,
gpc.Credentials,
)
if err != nil {
return nil, err
}
Expand All @@ -48,7 +43,6 @@ func newProvider(pc ProviderConfig, sc SessionConfig) (providers.Provider, error
singleFlightProvider = providers.NewSingleFlightProvider(googleProvider)
case providers.OktaProviderName:
opc := pc.OktaProviderConfig

oktaProvider, err := providers.NewOktaProvider(p, opc.OrgURL, opc.ServerID)
if err != nil {
return nil, err
Expand Down
71 changes: 43 additions & 28 deletions internal/auth/providers/google.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,21 @@ type GoogleProvider struct {
AdminService AdminService
cb *circuit.Breaker
GroupsCache groups.MemberSetCache

Prompt string
HostedDomain string
}

// NewGoogleProvider returns a new GoogleProvider and sets the provider url endpoints.
func NewGoogleProvider(p *ProviderData, impersonateUser, credsFilePath string) (*GoogleProvider, error) {
func NewGoogleProvider(p *ProviderData, prompt, hd, impersonate, credentials string) (*GoogleProvider, error) {
p.ProviderName = "Google"
p.SignInURL = &url.URL{Scheme: "https",
Host: "accounts.google.com",
Path: "/o/oauth2/auth",
// to get a refresh token. see https://developers.google.com/identity/protocols/OAuth2WebServer#offline
RawQuery: "access_type=offline",
Path: "/o/oauth2/v2/auth",
}
p.RedeemURL = &url.URL{Scheme: "https",
Host: "www.googleapis.com",
Path: "/oauth2/v3/token",
Path: "/oauth2/v4/token",
}
p.RevokeURL = &url.URL{Scheme: "https",
Host: "accounts.google.com",
Expand All @@ -51,15 +52,20 @@ func NewGoogleProvider(p *ProviderData, impersonateUser, credsFilePath string) (
Host: "www.googleapis.com",
Path: "/oauth2/v3/tokeninfo",
}
p.ProfileURL = &url.URL{}

if p.Scope == "" {
p.Scope = "profile email"
}

// not used for google
p.ProfileURL = &url.URL{}
if prompt == "" {
prompt = "consent"
}

googleProvider := &GoogleProvider{
ProviderData: p,
Prompt: prompt,
HostedDomain: hd,
}

googleProvider.cb = circuit.NewBreaker(&circuit.Options{
Expand All @@ -72,17 +78,19 @@ func NewGoogleProvider(p *ProviderData, impersonateUser, credsFilePath string) (
time.Duration(200)*time.Second, time.Duration(500)*time.Millisecond,
),
})
if credsFilePath != "" {
credsReader, err := os.Open(credsFilePath)

if credentials != "" {
credsReader, err := os.Open(credentials)
if err != nil {
return nil, errors.New("could not read google credentials file")
}

googleProvider.AdminService = &GoogleAdminService{
adminService: getAdminService(impersonateUser, credsReader),
adminService: getAdminService(impersonate, credsReader),
cb: googleProvider.cb,
}
}

return googleProvider, nil
}

Expand Down Expand Up @@ -113,30 +121,35 @@ func (p *GoogleProvider) ValidateSessionState(s *sessions.SessionState) bool {
return false
}

var endpoint url.URL
endpoint = *p.ValidateURL
q := endpoint.Query()
q.Add("access_token", s.AccessToken)
endpoint.RawQuery = q.Encode()
params := url.Values{}
params.Set("access_token", s.AccessToken)

err := p.googleRequest("POST", endpoint.String(), nil, []string{"action:validate"}, nil)
err := p.googleRequest("POST", p.ValidateURL.String(), params, []string{"action:validate"}, nil)
if err != nil {
return false
}

return true
}

// GetSignInURL returns the sign in url with typical oauth parameters
func (p *GoogleProvider) GetSignInURL(redirectURI, state string) string {
var a url.URL
a = *p.SignInURL
params, _ := url.ParseQuery(a.RawQuery)
params.Set("redirect_uri", redirectURI)
params.Set("approval_prompt", p.ApprovalPrompt)
params.Add("scope", p.Scope)

params := url.Values{}
params.Set("client_id", p.ClientID)
params.Set("response_type", "code")
params.Add("state", state)
params.Set("redirect_uri", redirectURI)
params.Set("scope", p.Scope)
params.Set("access_type", "offline")
params.Set("state", state)
params.Set("prompt", p.Prompt)

if p.HostedDomain != "" {
params.Set("hd", p.HostedDomain)
}

a.RawQuery = params.Encode()
return a.String()
}
Expand Down Expand Up @@ -287,11 +300,12 @@ func (p *GoogleProvider) Redeem(redirectURL, code string) (*sessions.SessionStat
if code == "" {
return nil, ErrBadRequest
}

params := url.Values{}
params.Add("redirect_uri", redirectURL)
params.Add("code", code)
params.Add("client_id", p.ClientID)
params.Add("client_secret", p.ClientSecret)
params.Add("code", code)
params.Add("redirect_uri", redirectURL)
params.Add("grant_type", "authorization_code")

var response struct {
Expand All @@ -311,6 +325,7 @@ func (p *GoogleProvider) Redeem(redirectURL, code string) (*sessions.SessionStat
if err != nil {
return nil, err
}

return &sessions.SessionState{
AccessToken: response.AccessToken,
RefreshToken: response.RefreshToken,
Expand Down Expand Up @@ -398,10 +413,10 @@ func (p *GoogleProvider) RefreshAccessToken(refreshToken string) (token string,
// https://developers.google.com/identity/protocols/OAuth2WebServer#refresh

params := url.Values{}
params.Add("client_id", p.ClientID)
params.Add("client_secret", p.ClientSecret)
params.Add("refresh_token", refreshToken)
params.Add("grant_type", "refresh_token")
params.Set("client_id", p.ClientID)
params.Set("client_secret", p.ClientSecret)
params.Set("refresh_token", refreshToken)
params.Set("grant_type", "refresh_token")

var response struct {
AccessToken string `json:"access_token"`
Expand All @@ -421,7 +436,7 @@ func (p *GoogleProvider) RefreshAccessToken(refreshToken string) (token string,
// Revoke revokes the access token a given session state.
func (p *GoogleProvider) Revoke(s *sessions.SessionState) error {
params := url.Values{}
params.Add("token", s.AccessToken)
params.Set("token", s.AccessToken)

err := p.googleRequest("GET", p.RevokeURL.String(), params, []string{"action:revoke"}, nil)

Expand Down
30 changes: 15 additions & 15 deletions internal/auth/providers/google_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func newGoogleProvider(providerData *ProviderData) *GoogleProvider {
ValidateURL: &url.URL{},
Scope: ""}
}
provider, _ := NewGoogleProvider(providerData, "", "")
provider, _ := NewGoogleProvider(providerData, "", "", "", "")
return provider
}

Expand All @@ -54,8 +54,8 @@ func TestGoogleProviderDefaults(t *testing.T) {
}{
{
name: "defaults",
signInURL: "https://accounts.google.com/o/oauth2/auth?access_type=offline",
redeemURL: "https://www.googleapis.com/oauth2/v3/token",
signInURL: "https://accounts.google.com/o/oauth2/v2/auth",
redeemURL: "https://www.googleapis.com/oauth2/v4/token",
revokeURL: "https://accounts.google.com/o/oauth2/revoke",
validateURL: "https://www.googleapis.com/oauth2/v3/tokeninfo",
scope: "profile email",
Expand All @@ -71,38 +71,38 @@ func TestGoogleProviderDefaults(t *testing.T) {
t.Errorf("expected provider name Google, got %s", p.Data().ProviderName)
}
if p.Data().SignInURL.String() != expected.signInURL {
log.Printf("expected %s", expected.signInURL)
log.Printf("got %s", p.Data().SignInURL.String())
t.Errorf("want: %v", expected.signInURL)
t.Errorf("have: %v", p.Data().SignInURL.String())
t.Errorf("unexpected signin url")
}

if p.Data().RedeemURL.String() != expected.redeemURL {
log.Printf("expected %s", expected.redeemURL)
log.Printf("got %s", p.Data().RedeemURL.String())
t.Errorf("want: %v", expected.redeemURL)
t.Errorf("have: %v", p.Data().RedeemURL.String())
t.Errorf("unexpected redeem url")
}

if p.Data().RevokeURL.String() != expected.revokeURL {
log.Printf("expected %s", expected.revokeURL)
log.Printf("got %s", p.Data().RevokeURL.String())
t.Errorf("want: %v", expected.revokeURL)
t.Errorf("have: %v", p.Data().RevokeURL.String())
t.Errorf("unexpected revoke url")
}

if p.Data().ValidateURL.String() != expected.validateURL {
log.Printf("expected %s", expected.validateURL)
log.Printf("got %s", p.Data().ValidateURL.String())
t.Errorf("want: %v", expected.validateURL)
t.Errorf("have: %v", p.Data().ValidateURL.String())
t.Errorf("unexpected validate url")
}

if p.Data().ProfileURL.String() != expected.profileURL {
log.Printf("expected %s", expected.profileURL)
log.Printf("got %s", p.Data().ProfileURL.String())
t.Errorf("want: %v", expected.profileURL)
t.Errorf("have: %v", p.Data().ProfileURL.String())
t.Errorf("unexpected profile url")
}

if p.Data().Scope != expected.scope {
log.Printf("expected %s", expected.scope)
log.Printf("got %s", p.Data().Scope)
t.Errorf("want: %v", expected.scope)
t.Errorf("have: %v", p.Data().Scope)
t.Errorf("unexpected scope")
}
})
Expand Down
25 changes: 14 additions & 11 deletions internal/auth/providers/provider_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ import (
// ProviderData holds the fields associated with providers
// necessary to implement the Provider interface.
type ProviderData struct {
ProviderName string
ProviderSlug string
ClientID string
ClientSecret string
SignInURL *url.URL
RedeemURL *url.URL
RevokeURL *url.URL
ProfileURL *url.URL
ValidateURL *url.URL
Scope string
ApprovalPrompt string
ProviderName string
ProviderSlug string

ClientID string
ClientSecret string

SignInURL *url.URL
RedeemURL *url.URL
RevokeURL *url.URL
ValidateURL *url.URL
ProfileURL *url.URL

Scope string

SessionLifetimeTTL time.Duration
}

Expand Down
1 change: 0 additions & 1 deletion internal/auth/providers/provider_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ func (p *ProviderData) GetSignInURL(redirectURI, state string) string {
a = *p.SignInURL
params, _ := url.ParseQuery(a.RawQuery)
params.Set("redirect_uri", redirectURI)
params.Set("approval_prompt", p.ApprovalPrompt)
params.Add("scope", p.Scope)
params.Set("client_id", p.ClientID)
params.Set("response_type", "code")
Expand Down

0 comments on commit 568049a

Please sign in to comment.