influxdb/chronograf/oauth2/heroku.go

146 lines
3.6 KiB
Go

package oauth2
import (
"encoding/json"
"fmt"
"net/http"
"github.com/influxdata/influxdb/v2/chronograf"
"golang.org/x/oauth2"
hrk "golang.org/x/oauth2/heroku"
)
// Ensure that Heroku is an oauth2.Provider
var _ Provider = &Heroku{}
const (
// HerokuAccountRoute is required for interacting with Heroku API
HerokuAccountRoute string = "https://api.heroku.com/account"
)
// Heroku is an OAuth2 Provider allowing users to authenticate with Heroku to
// gain access to Chronograf
type Heroku struct {
// OAuth2 Secrets
ClientID string
ClientSecret string
Organizations []string // set of organizations permitted to access the protected resource. Empty means "all"
Logger chronograf.Logger
}
// Config returns the OAuth2 exchange information and endpoints
func (h *Heroku) Config() *oauth2.Config {
return &oauth2.Config{
ClientID: h.ID(),
ClientSecret: h.Secret(),
Scopes: h.Scopes(),
Endpoint: hrk.Endpoint,
}
}
// ID returns the Heroku application client ID
func (h *Heroku) ID() string {
return h.ClientID
}
// Name returns the name of this provider (heroku)
func (h *Heroku) Name() string {
return "heroku"
}
// PrincipalID returns the Heroku email address of the user.
func (h *Heroku) PrincipalID(provider *http.Client) (string, error) {
type DefaultOrg struct {
ID string `json:"id"`
Name string `json:"name"`
}
type Account struct {
Email string `json:"email"`
DefaultOrganization DefaultOrg `json:"default_organization"`
}
req, err := http.NewRequest("GET", HerokuAccountRoute, nil)
if err != nil {
return "", err
}
// Requests fail to Heroku unless this Accept header is set.
req.Header.Set("Accept", "application/vnd.heroku+json; version=3")
resp, err := provider.Do(req)
if resp.StatusCode/100 != 2 {
err := fmt.Errorf(
"unable to GET user data from %s. Status: %s",
HerokuAccountRoute,
resp.Status,
)
h.Logger.Error("", err)
return "", err
}
if err != nil {
h.Logger.Error("Unable to communicate with Heroku. err:", err)
return "", err
}
defer resp.Body.Close()
d := json.NewDecoder(resp.Body)
var account Account
if err := d.Decode(&account); err != nil {
h.Logger.Error("Unable to decode response from Heroku. err:", err)
return "", err
}
// check if member of org
if len(h.Organizations) > 0 {
for _, org := range h.Organizations {
if account.DefaultOrganization.Name == org {
return account.Email, nil
}
}
h.Logger.Error(ErrOrgMembership)
return "", ErrOrgMembership
}
return account.Email, nil
}
// Group returns the Heroku organization that user belongs to.
func (h *Heroku) Group(provider *http.Client) (string, error) {
type DefaultOrg struct {
ID string `json:"id"`
Name string `json:"name"`
}
type Account struct {
Email string `json:"email"`
DefaultOrganization DefaultOrg `json:"default_organization"`
}
resp, err := provider.Get(HerokuAccountRoute)
if err != nil {
h.Logger.Error("Unable to communicate with Heroku. err:", err)
return "", err
}
defer resp.Body.Close()
d := json.NewDecoder(resp.Body)
var account Account
if err := d.Decode(&account); err != nil {
h.Logger.Error("Unable to decode response from Heroku. err:", err)
return "", err
}
return account.DefaultOrganization.Name, nil
}
// Scopes for heroku is "identity" which grants access to user account
// information. This will grant us access to the user's email address which is
// used as the Principal's identifier.
func (h *Heroku) Scopes() []string {
return []string{"identity"}
}
// Secret returns the Heroku application client secret
func (h *Heroku) Secret() string {
return h.ClientSecret
}