Skip to content
This repository has been archived by the owner on Jan 24, 2019. It is now read-only.

Commit

Permalink
add heroku provider
Browse files Browse the repository at this point in the history
  • Loading branch information
benburkert committed Jan 5, 2017
1 parent 89ba1d8 commit 6837be7
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 0 deletions.
76 changes: 76 additions & 0 deletions providers/heroku.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package providers

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
)

type HerokuProvider struct {
*ProviderData
}

func NewHerokuProvider(p *ProviderData) *HerokuProvider {
const (
idHost = "id.heroku.com"
apiHost = "api.heroku.com"
)

p.ProviderName = "Heroku"
if p.LoginURL.String() == "" {
p.LoginURL = &url.URL{Scheme: "https",
Host: idHost,
Path: "/oauth/authorize"}
}
if p.RedeemURL.String() == "" {
p.RedeemURL = &url.URL{Scheme: "https",
Host: idHost,
Path: "/oauth/token"}
}
if p.ValidateURL.String() == "" {
p.ValidateURL = &url.URL{Scheme: "https",
Host: idHost,
Path: "/oauth/authorizations"}
}
if p.ProfileURL.String() == "" {
p.ProfileURL = &url.URL{Scheme: "https",
Host: apiHost,
Path: "/account"}
}
if p.Scope == "" {
p.Scope = "identity"
}
return &HerokuProvider{ProviderData: p}
}

func (p *HerokuProvider) GetEmailAddress(s *SessionState) (string, error) {
req, _ := http.NewRequest("GET", p.ProfileURL.String(), nil)
req.Header.Set("Authorization", "Bearer "+s.AccessToken)
req.Header.Set("Accept", "application/vnd.heroku+json; version=3")

resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}

body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return "", err
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("got %d from %q %s", resp.StatusCode, p.ProfileURL, body)
}

var account struct {
Email string `json:"email"`
}

if err := json.Unmarshal(body, &account); err != nil {
return "", fmt.Errorf("%s unmarshaling %s", err, body)
}

return account.Email, nil
}
121 changes: 121 additions & 0 deletions providers/heroku_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package providers

import (
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/bmizerany/assert"
)

func testHerokuProvider(hostname string) *HerokuProvider {
p := NewHerokuProvider(
&ProviderData{
ProviderName: "",
LoginURL: &url.URL{},
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
Scope: ""})
if hostname != "" {
updateURL(p.Data().LoginURL, hostname)
updateURL(p.Data().RedeemURL, hostname)
updateURL(p.Data().ProfileURL, hostname)
}
return p
}

func testHerokuBackend(payload string) *httptest.Server {
path := "/account"

return httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
url := r.URL
if url.Path != path {
w.WriteHeader(404)
} else if r.Header.Get("Authorization") != "Bearer imaginary_access_token" {
w.WriteHeader(403)
} else {
w.WriteHeader(200)
w.Write([]byte(payload))
}
}))
}

func TestHerokuProviderDefaults(t *testing.T) {
p := testHerokuProvider("")
assert.NotEqual(t, nil, p)
assert.Equal(t, "Heroku", p.Data().ProviderName)
assert.Equal(t, "https://id.heroku.com/oauth/authorize",
p.Data().LoginURL.String())
assert.Equal(t, "https://id.heroku.com/oauth/token",
p.Data().RedeemURL.String())
assert.Equal(t, "https://api.heroku.com/account",
p.Data().ProfileURL.String())
assert.Equal(t, "https://id.heroku.com/oauth/authorizations",
p.Data().ValidateURL.String())
assert.Equal(t, "identity", p.Data().Scope)
}

func TestHerokuProviderOverrides(t *testing.T) {
p := NewHerokuProvider(
&ProviderData{
LoginURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/oauth/auth"},
RedeemURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/oauth/token"},
ProfileURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/oauth/profile"},
ValidateURL: &url.URL{
Scheme: "https",
Host: "example.com",
Path: "/oauth/tokeninfo"},
Scope: "profile"})
assert.NotEqual(t, nil, p)
assert.Equal(t, "Heroku", p.Data().ProviderName)
assert.Equal(t, "https://example.com/oauth/auth",
p.Data().LoginURL.String())
assert.Equal(t, "https://example.com/oauth/token",
p.Data().RedeemURL.String())
assert.Equal(t, "https://example.com/oauth/profile",
p.Data().ProfileURL.String())
assert.Equal(t, "https://example.com/oauth/tokeninfo",
p.Data().ValidateURL.String())
assert.Equal(t, "profile", p.Data().Scope)
}

func TestHerokuProviderGetEmailAddress(t *testing.T) {
b := testHerokuBackend(`{"email": "[email protected]"}`)
defer b.Close()

b_url, _ := url.Parse(b.URL)
p := testHerokuProvider(b_url.Host)

session := &SessionState{AccessToken: "imaginary_access_token"}
email, err := p.GetEmailAddress(session)
assert.Equal(t, nil, err)
assert.Equal(t, "[email protected]", email)
}

func TestHerokuProviderGetEmailAddressFailedRequest(t *testing.T) {
b := testHerokuBackend("unused payload")
defer b.Close()

b_url, _ := url.Parse(b.URL)
p := testHerokuProvider(b_url.Host)

// We'll trigger a request failure by using an unexpected access
// token. Alternatively, we could allow the parsing of the payload as
// JSON to fail.
session := &SessionState{AccessToken: "unexpected_access_token"}
email, err := p.GetEmailAddress(session)
assert.NotEqual(t, nil, err)
assert.Equal(t, "", email)
}
2 changes: 2 additions & 0 deletions providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ func New(provider string, p *ProviderData) Provider {
return NewAzureProvider(p)
case "gitlab":
return NewGitLabProvider(p)
case "heroku":
return NewHerokuProvider(p)
default:
return NewGoogleProvider(p)
}
Expand Down

0 comments on commit 6837be7

Please sign in to comment.