You've already forked step-ca-cli
mirror of
https://github.com/smallstep/cli.git
synced 2025-08-09 03:22:43 +03:00
Add support for OAuth using GitHub
It adds the header "Accept: application/json" so OAuth services like GitHub returns the data in the appropriate form instead of using application/x-www-form-urlencoded. It also configures GitHub as a new provider as it does not have a well-known url. This header does not cause any issues on Google or Microsoft. Fixes #740
This commit is contained in:
@@ -328,8 +328,8 @@ type options struct {
|
||||
|
||||
// Validate validates the options.
|
||||
func (o *options) Validate() error {
|
||||
if o.Provider != "google" && !strings.HasPrefix(o.Provider, "https://") {
|
||||
return errors.New("use a valid provider: google")
|
||||
if o.Provider != "google" && o.Provider != "github" && !strings.HasPrefix(o.Provider, "https://") {
|
||||
return errors.New("use a valid provider: google or github")
|
||||
}
|
||||
if o.CallbackListener != "" {
|
||||
if _, _, err := net.SplitHostPort(o.CallbackListener); err != nil {
|
||||
@@ -563,6 +563,28 @@ type oauth struct {
|
||||
tokCh chan *token
|
||||
}
|
||||
|
||||
type endpoint struct {
|
||||
authorization string
|
||||
deviceAuthorization string
|
||||
token string
|
||||
userInfo string
|
||||
}
|
||||
|
||||
var knownProviders = map[string]endpoint{
|
||||
"google": {
|
||||
authorization: "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
deviceAuthorization: "https://oauth2.googleapis.com/device/code",
|
||||
token: "https://www.googleapis.com/oauth2/v4/token",
|
||||
userInfo: "https://www.googleapis.com/oauth2/v3/userinfo",
|
||||
},
|
||||
"github": {
|
||||
authorization: "https://github.com/login/oauth/authorize",
|
||||
deviceAuthorization: "https://github.com/login/device/code",
|
||||
token: "https://github.com/login/oauth/access_token",
|
||||
userInfo: "https://api.github.com/user",
|
||||
},
|
||||
}
|
||||
|
||||
func newOauth(provider, clientID, clientSecret, authzEp, deviceAuthzEp, tokenEp, scope, prompt string, authParams url.Values, opts *options) (*oauth, error) {
|
||||
state, err := randutil.Alphanumeric(32)
|
||||
if err != nil {
|
||||
@@ -579,18 +601,18 @@ func newOauth(provider, clientID, clientSecret, authzEp, deviceAuthzEp, tokenEp,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch provider {
|
||||
case "google":
|
||||
// Check known providers
|
||||
if p, ok := knownProviders[provider]; ok {
|
||||
return &oauth{
|
||||
provider: provider,
|
||||
clientID: clientID,
|
||||
clientSecret: clientSecret,
|
||||
scope: scope,
|
||||
prompt: prompt,
|
||||
authzEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
|
||||
deviceAuthzEndpoint: "https://oauth2.googleapis.com/device/code",
|
||||
tokenEndpoint: "https://www.googleapis.com/oauth2/v4/token",
|
||||
userInfoEndpoint: "https://www.googleapis.com/oauth2/v3/userinfo",
|
||||
authzEndpoint: p.authorization,
|
||||
deviceAuthzEndpoint: p.deviceAuthorization,
|
||||
tokenEndpoint: p.token,
|
||||
userInfoEndpoint: p.userInfo,
|
||||
loginHint: opts.Email,
|
||||
state: state,
|
||||
codeChallenge: challenge,
|
||||
@@ -605,13 +627,12 @@ func newOauth(provider, clientID, clientSecret, authzEp, deviceAuthzEp, tokenEp,
|
||||
errCh: make(chan error),
|
||||
tokCh: make(chan *token),
|
||||
}, nil
|
||||
default:
|
||||
userinfoEp := ""
|
||||
}
|
||||
|
||||
userinfoEp := ""
|
||||
isDeviceFlow := opts.Console && opts.ConsoleFlow == deviceConsoleFlow
|
||||
|
||||
if (isDeviceFlow && deviceAuthzEp == "" && tokenEp == "") ||
|
||||
(!isDeviceFlow && authzEp == "" && tokenEp == "") {
|
||||
if (isDeviceFlow && deviceAuthzEp == "" && tokenEp == "") || (!isDeviceFlow && authzEp == "" && tokenEp == "") {
|
||||
d, err := disco(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -659,7 +680,6 @@ func newOauth(provider, clientID, clientSecret, authzEp, deviceAuthzEp, tokenEp,
|
||||
tokCh: make(chan *token),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func disco(provider string) (map[string]interface{}, error) {
|
||||
u, err := url.Parse(provider)
|
||||
@@ -688,6 +708,19 @@ func disco(provider string) (map[string]interface{}, error) {
|
||||
return details, err
|
||||
}
|
||||
|
||||
// postForm simulates http.PostForm but adds the header "Accept:
|
||||
// application/json", without this header GitHub will use
|
||||
// application/x-www-form-urlencoded.
|
||||
func postForm(url string, data url.Values) (*http.Response, error) {
|
||||
req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create POST %s request failed: %w", url, err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
return http.DefaultClient.Do(req)
|
||||
}
|
||||
|
||||
// NewServer creates http server
|
||||
func (o *oauth) NewServer() (*httptest.Server, error) {
|
||||
if o.CallbackListener == "" {
|
||||
@@ -826,7 +859,8 @@ func (o *oauth) DoDeviceAuthorization() (*token, error) {
|
||||
data.Set("client_secret", o.clientSecret)
|
||||
data.Set("scope", o.scope)
|
||||
|
||||
resp, err := http.PostForm(o.deviceAuthzEndpoint, data)
|
||||
resp, err := postForm(o.deviceAuthzEndpoint, data)
|
||||
// resp, err := http.PostForm(o.deviceAuthzEndpoint, data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "http failure to identify device")
|
||||
}
|
||||
@@ -913,7 +947,8 @@ func openBrowserIfAsked(o *oauth, u string) {
|
||||
var errHTTPToken = errors.New("bad request; token not returned")
|
||||
|
||||
func (o *oauth) deviceAuthzTokenPoll(data url.Values) (*token, error) {
|
||||
resp, err := http.PostForm(o.tokenEndpoint, data)
|
||||
resp, err := postForm(o.tokenEndpoint, data)
|
||||
// resp, err := http.PostForm(o.tokenEndpoint, data)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error doing POST to /token endpoint")
|
||||
}
|
||||
@@ -986,7 +1021,8 @@ func (o *oauth) DoTwoLeggedAuthorization(issuer string) (*token, error) {
|
||||
}
|
||||
|
||||
// Send the POST request and return token.
|
||||
resp, err := http.PostForm(o.tokenEndpoint, params)
|
||||
resp, err := postForm(o.tokenEndpoint, params)
|
||||
// resp, err := http.PostForm(o.tokenEndpoint, params)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error from token endpoint")
|
||||
}
|
||||
@@ -1195,7 +1231,8 @@ func (o *oauth) Exchange(tokenEndpoint, code string) (*token, error) {
|
||||
|
||||
//nolint:gosec // Tainted url deemed acceptable. Not used to store any
|
||||
// backend data.
|
||||
resp, err := http.PostForm(tokenEndpoint, data)
|
||||
resp, err := postForm(tokenEndpoint, data)
|
||||
// resp, err := http.PostForm(tokenEndpoint, data)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user