Skip to content

Commit

Permalink
Merge pull request dexidp#179 from bcwaldon/cli-ents
Browse files Browse the repository at this point in the history
Drive authctl new-client with API
  • Loading branch information
bcwaldon committed Feb 5, 2015
2 parents 0300995 + 8110d9d commit cce66a8
Show file tree
Hide file tree
Showing 15 changed files with 441 additions and 50 deletions.
8 changes: 1 addition & 7 deletions cmd/authctl/command_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,7 @@ func runNewClient(args []string) int {
return 1
}

drv, err := newDBDriver(global.dbURL)
if err != nil {
stderr("Unable to initialize driver: %v", err)
return 1
}

cc, err := drv.NewClient(oidc.ClientMetadata{RedirectURL: *redirectURL})
cc, err := getDriver().NewClient(oidc.ClientMetadata{RedirectURL: *redirectURL})
if err != nil {
stderr("Failed creating new client: %v", err)
return 1
Expand Down
16 changes: 2 additions & 14 deletions cmd/authctl/command_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,7 @@ func runSetConnectorConfigs(args []string) int {
return 1
}

drv, err := newDBDriver(global.dbURL)
if err != nil {
stderr("Unable to initialize driver: %v", err)
return 1
}

if err := drv.SetConnectorConfigs(cfgs); err != nil {
if err := getDriver().SetConnectorConfigs(cfgs); err != nil {
stderr(err.Error())
return 1
}
Expand All @@ -67,13 +61,7 @@ func runGetConnectorConfigs(args []string) int {
return 2
}

drv, err := newDBDriver(global.dbURL)
if err != nil {
stderr("Unable to initialize driver: %v", err)
return 1
}

cfgs, err := drv.ConnectorConfigs()
cfgs, err := getDriver().ConnectorConfigs()
if err != nil {
stderr("Unable to retrieve connector configs: %v", err)
return 1
Expand Down
67 changes: 67 additions & 0 deletions cmd/authctl/driver_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"errors"
"net/http"

"github.com/coreos-inc/auth/connector"
"github.com/coreos-inc/auth/oidc"
"github.com/coreos-inc/auth/schema"
)

func newAPIDriver(pcfg oidc.ProviderConfig, creds oidc.ClientCredentials) (driver, error) {
ccfg := oidc.ClientConfig{
ProviderConfig: pcfg,
Credentials: creds,
}
oc, err := oidc.NewClient(ccfg)
if err != nil {
return nil, err
}

trans := &oidc.AuthenticatedTransport{
TokenRefresher: &oidc.ClientCredsTokenRefresher{
Issuer: pcfg.Issuer,
OIDCClient: oc,
},
RoundTripper: http.DefaultTransport,
}
hc := &http.Client{Transport: trans}
svc, err := schema.NewWithBasePath(hc, pcfg.Issuer)
if err != nil {
return nil, err
}

return &apiDriver{svc: svc}, nil
}

type apiDriver struct {
svc *schema.Service
}

func (d *apiDriver) NewClient(meta oidc.ClientMetadata) (*oidc.ClientCredentials, error) {
sc := &schema.Client{
RedirectURIs: []string{meta.RedirectURL.String()},
}

call := d.svc.Clients.Create(sc)
scs, err := call.Do()
if err != nil {
return nil, err
}

creds := &oidc.ClientCredentials{
ID: scs.Id,
Secret: scs.Secret,
}

return creds, nil
}

func (d *apiDriver) ConnectorConfigs() ([]connector.ConnectorConfig, error) {
return nil, errors.New("unable to get connector configs from HTTP API")
}

func (d *apiDriver) SetConnectorConfigs(cfgs []connector.ConnectorConfig) error {
return errors.New("unable to set connector configs through HTTP API")
}
38 changes: 30 additions & 8 deletions cmd/authctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package main
import (
"errors"
"flag"
"net/http"
"os"

"github.com/coreos-inc/auth/oidc"
pflag "github.com/coreos-inc/auth/pkg/flag"
"github.com/coreos-inc/auth/pkg/log"
)
Expand All @@ -17,6 +19,8 @@ var (
globalFS = flag.NewFlagSet(cliName, flag.ExitOnError)

global struct {
endpoint string
creds oidc.ClientCredentials
dbURL string
help bool
logDebug bool
Expand All @@ -26,6 +30,9 @@ var (
func init() {
log.EnableTimestamps()

globalFS.StringVar(&global.endpoint, "endpoint", "", "URL of authd API")
globalFS.StringVar(&global.creds.ID, "client-id", "", "authd API user ID")
globalFS.StringVar(&global.creds.Secret, "client-secret", "", "authd API user password")
globalFS.StringVar(&global.dbURL, "db-url", "", "DSN-formatted database connection string")
globalFS.BoolVar(&global.help, "help", false, "Print usage information and exit")
globalFS.BoolVar(&global.help, "h", false, "Print usage information and exit")
Expand Down Expand Up @@ -80,18 +87,33 @@ type command struct {

}

func parseFlags() (err error) {
if err = globalFS.Parse(os.Args[1:]); err != nil {
return
func parseFlags() error {
if err := globalFS.Parse(os.Args[1:]); err != nil {
return err
}

if err = pflag.SetFlagsFromEnv(globalFS, "AUTHCTL"); err != nil {
return
return pflag.SetFlagsFromEnv(globalFS, "AUTHCTL")
}

func getDriver() (drv driver) {
var err error
switch {
case len(global.dbURL) > 0:
drv, err = newDBDriver(global.dbURL)
case len(global.endpoint) > 0:
if len(global.creds.ID) == 0 || len(global.creds.Secret) == 0 {
err = errors.New("--client-id/--client-secret flags unset")
} else {
pcfg := oidc.WaitForProviderConfig(http.DefaultClient, global.endpoint)
drv, err = newAPIDriver(pcfg, global.creds)
}
default:
err = errors.New("--endpoint/--db-url flags unset")
}

if len(global.dbURL) == 0 {
err = errors.New("--db-url unset")
return
if err != nil {
stderr("Unable to configure authctl driver: %v", err)
os.Exit(1)
}

return
Expand Down
3 changes: 2 additions & 1 deletion oauth2/oauth2_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package oauth2

import (
"errors"
"net/url"
"reflect"
"strings"
Expand Down Expand Up @@ -129,7 +130,7 @@ func TestParseAuthCodeRequest(t *testing.T) {
}

func TestClientCredsToken(t *testing.T) {
hc := &phttp.RequestRecorder{}
hc := &phttp.RequestRecorder{Error: errors.New("error")}
cfg := Config{
Credentials: ClientCredentials{ID: "cid", Secret: "csecret"},
Scope: []string{"foo-scope", "bar-scope"},
Expand Down
21 changes: 21 additions & 0 deletions oidc/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,24 @@ func FetchProviderConfig(hc phttp.Client, issuerURL string) (ProviderConfig, err
g := NewHTTPProviderConfigGetter(hc, issuerURL)
return g.Get()
}

func WaitForProviderConfig(hc phttp.Client, issuerURL string) (pcfg ProviderConfig) {
return waitForProviderConfig(hc, issuerURL, clockwork.NewRealClock())
}

func waitForProviderConfig(hc phttp.Client, issuerURL string, clock clockwork.Clock) (pcfg ProviderConfig) {
var sleep time.Duration
var err error
for {
pcfg, err = FetchProviderConfig(hc, issuerURL)
if err == nil {
break
}

sleep = ptime.ExpBackoff(sleep, time.Minute)
fmt.Printf("Failed fetching provider config, trying again in %v: %v", sleep, err)
time.Sleep(sleep)
}

return
}
30 changes: 30 additions & 0 deletions oidc/provider_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package oidc

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"testing"
Expand Down Expand Up @@ -434,3 +436,31 @@ func TestProviderConfigSupportsGrantType(t *testing.T) {
}
}
}

func TestWaitForProviderConfigImmediateSuccess(t *testing.T) {
cfg := ProviderConfig{Issuer: "http://example.com"}
b, err := json.Marshal(cfg)
if err != nil {
t.Fatalf("Failed marshaling provider config")
}

resp := http.Response{Body: ioutil.NopCloser(bytes.NewBuffer(b))}
hc := &phttp.RequestRecorder{Response: &resp}
fc := clockwork.NewFakeClock()

reschan := make(chan ProviderConfig)
go func() {
reschan <- waitForProviderConfig(hc, cfg.Issuer, fc)
}()

var got ProviderConfig
select {
case got = <-reschan:
case <-time.After(time.Second):
t.Fatalf("Did not receive result within 1s")
}

if !reflect.DeepEqual(cfg, got) {
t.Fatalf("Received incorrect provider config: want=%#v got=%#v", cfg, got)
}
}
79 changes: 79 additions & 0 deletions oidc/transport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package oidc

import (
"fmt"
"net/http"
"sync"

"github.com/coreos-inc/auth/jose"
phttp "github.com/coreos-inc/auth/pkg/http"
)

type TokenRefresher interface {
// Verify checks if the provided token is currently valid or not.
Verify(jose.JWT) error

// Refresh attempts to authenticate and retrieve a new token.
Refresh() (jose.JWT, error)
}

type ClientCredsTokenRefresher struct {
Issuer string
OIDCClient *Client
}

func (c *ClientCredsTokenRefresher) Verify(jwt jose.JWT) (err error) {
_, err = VerifyClientClaims(jwt, c.Issuer)
return
}

func (c *ClientCredsTokenRefresher) Refresh() (jwt jose.JWT, err error) {
if err = c.OIDCClient.Healthy(); err != nil {
err = fmt.Errorf("unable to authenticate, unhealthy OIDC client: %v", err)
return
}

jwt, err = c.OIDCClient.ClientCredsToken([]string{"openid"})
if err != nil {
err = fmt.Errorf("unable to verify auth code with issuer: %v", err)
return
}

return
}

type AuthenticatedTransport struct {
TokenRefresher
http.RoundTripper

mu sync.Mutex
jwt jose.JWT
}

func (t *AuthenticatedTransport) verifiedJWT() (jose.JWT, error) {
t.mu.Lock()
defer t.mu.Unlock()

if t.TokenRefresher.Verify(t.jwt) == nil {
return t.jwt, nil
}

jwt, err := t.TokenRefresher.Refresh()
if err != nil {
return jose.JWT{}, fmt.Errorf("unable to acquire valid JWT: %v", err)
}

t.jwt = jwt
return t.jwt, nil
}

func (t *AuthenticatedTransport) RoundTrip(r *http.Request) (*http.Response, error) {
jwt, err := t.verifiedJWT()
if err != nil {
return nil, err
}

req := phttp.CopyRequest(r)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", jwt.Encode()))
return t.RoundTripper.RoundTrip(req)
}
Loading

0 comments on commit cce66a8

Please sign in to comment.