2015-08-18 05:57:27 +05:30
|
|
|
package main
|
|
|
|
|
2016-06-09 02:23:26 +05:30
|
|
|
//go:generate go-bindata -pkg main -o assets.go data/
|
|
|
|
//go:generate gofmt -w assets.go
|
|
|
|
|
2015-08-18 05:57:27 +05:30
|
|
|
import (
|
|
|
|
"bytes"
|
2015-08-27 02:58:59 +05:30
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
2015-08-18 05:57:27 +05:30
|
|
|
"encoding/json"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
2016-06-09 02:23:26 +05:30
|
|
|
"html/template"
|
2015-08-27 02:58:59 +05:30
|
|
|
"io/ioutil"
|
2015-08-18 05:57:27 +05:30
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"os"
|
2016-07-26 05:36:20 +05:30
|
|
|
"path"
|
2016-06-09 02:23:26 +05:30
|
|
|
"strings"
|
2015-08-18 05:57:27 +05:30
|
|
|
"time"
|
|
|
|
|
2016-05-07 00:14:54 +05:30
|
|
|
"github.com/coreos/go-oidc/jose"
|
|
|
|
"github.com/coreos/go-oidc/oauth2"
|
|
|
|
"github.com/coreos/go-oidc/oidc"
|
|
|
|
|
2016-06-20 23:33:43 +05:30
|
|
|
"github.com/coreos/dex/client"
|
2015-08-18 05:57:27 +05:30
|
|
|
pflag "github.com/coreos/dex/pkg/flag"
|
|
|
|
phttp "github.com/coreos/dex/pkg/http"
|
|
|
|
"github.com/coreos/dex/pkg/log"
|
2016-06-09 02:23:26 +05:30
|
|
|
"github.com/coreos/dex/scope"
|
2015-08-18 05:57:27 +05:30
|
|
|
)
|
|
|
|
|
2016-06-09 02:23:26 +05:30
|
|
|
var indexTemplate *template.Template
|
|
|
|
|
2015-08-18 05:57:27 +05:30
|
|
|
func main() {
|
|
|
|
fs := flag.NewFlagSet("oidc-app", flag.ExitOnError)
|
|
|
|
listen := fs.String("listen", "http://127.0.0.1:5555", "")
|
|
|
|
redirectURL := fs.String("redirect-url", "http://127.0.0.1:5555/callback", "")
|
2016-02-24 03:17:13 +05:30
|
|
|
clientID := fs.String("client-id", "example-app", "")
|
|
|
|
clientSecret := fs.String("client-secret", "ZXhhbXBsZS1hcHAtc2VjcmV0", "")
|
2015-08-27 05:42:06 +05:30
|
|
|
caFile := fs.String("trusted-ca-file", "", "the TLS CA file, if empty then the host's root CA will be used")
|
2015-08-27 02:58:59 +05:30
|
|
|
|
2016-06-03 17:21:00 +05:30
|
|
|
certFile := fs.String("tls-cert-file", "", "the TLS cert file. If empty, the app will listen on HTTP")
|
|
|
|
keyFile := fs.String("tls-key-file", "", "the TLS key file. If empty, the app will listen on HTTP")
|
|
|
|
|
2016-07-25 21:35:28 +05:30
|
|
|
discovery := fs.String("discovery", "http://127.0.0.1:5556/dex", "")
|
2015-08-18 05:57:27 +05:30
|
|
|
logDebug := fs.Bool("log-debug", false, "log debug-level information")
|
|
|
|
logTimestamps := fs.Bool("log-timestamps", false, "prefix log lines with timestamps")
|
|
|
|
|
|
|
|
if err := fs.Parse(os.Args[1:]); err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := pflag.SetFlagsFromEnv(fs, "EXAMPLE_APP"); err != nil {
|
|
|
|
fmt.Fprintln(os.Stderr, err.Error())
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if *logDebug {
|
|
|
|
log.EnableDebug()
|
|
|
|
}
|
|
|
|
if *logTimestamps {
|
|
|
|
log.EnableTimestamps()
|
|
|
|
}
|
|
|
|
|
|
|
|
if *clientID == "" {
|
|
|
|
log.Fatal("--client-id must be set")
|
|
|
|
}
|
|
|
|
|
|
|
|
if *clientSecret == "" {
|
|
|
|
log.Fatal("--client-secret must be set")
|
|
|
|
}
|
|
|
|
|
|
|
|
l, err := url.Parse(*listen)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to use --listen flag: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, p, err := net.SplitHostPort(l.Host)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to parse host from --listen flag: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-06-03 17:21:00 +05:30
|
|
|
redirectURLParsed, err := url.Parse(*redirectURL)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to parse url from --redirect-url flag: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
useTLS := *keyFile != "" && *certFile != ""
|
|
|
|
if useTLS && (redirectURLParsed.Scheme != "https" || l.Scheme != "https") {
|
2016-06-04 05:32:30 +05:30
|
|
|
log.Fatalf(`TLS Cert File and Key File were provided. Ensure listen and redirect URLs are using the "https://" scheme.`)
|
2016-06-03 17:21:00 +05:30
|
|
|
}
|
|
|
|
|
2015-08-18 05:57:27 +05:30
|
|
|
cc := oidc.ClientCredentials{
|
|
|
|
ID: *clientID,
|
|
|
|
Secret: *clientSecret,
|
|
|
|
}
|
|
|
|
|
2015-08-27 02:58:59 +05:30
|
|
|
var tlsConfig tls.Config
|
|
|
|
if *caFile != "" {
|
|
|
|
roots := x509.NewCertPool()
|
|
|
|
pemBlock, err := ioutil.ReadFile(*caFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to read ca file: %v", err)
|
|
|
|
}
|
|
|
|
roots.AppendCertsFromPEM(pemBlock)
|
|
|
|
tlsConfig.RootCAs = roots
|
|
|
|
}
|
|
|
|
|
|
|
|
httpClient := &http.Client{Transport: &http.Transport{TLSClientConfig: &tlsConfig}}
|
|
|
|
|
2015-08-18 05:57:27 +05:30
|
|
|
var cfg oidc.ProviderConfig
|
|
|
|
for {
|
2015-08-27 02:58:59 +05:30
|
|
|
cfg, err = oidc.FetchProviderConfig(httpClient, *discovery)
|
2015-08-18 05:57:27 +05:30
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
sleep := 3 * time.Second
|
|
|
|
log.Errorf("Failed fetching provider config, trying again in %v: %v", sleep, err)
|
|
|
|
time.Sleep(sleep)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Infof("Fetched provider config from %s: %#v", *discovery, cfg)
|
|
|
|
|
|
|
|
ccfg := oidc.ClientConfig{
|
2015-08-27 02:58:59 +05:30
|
|
|
HTTPClient: httpClient,
|
2015-08-18 05:57:27 +05:30
|
|
|
ProviderConfig: cfg,
|
|
|
|
Credentials: cc,
|
|
|
|
RedirectURL: *redirectURL,
|
2016-05-07 00:14:54 +05:30
|
|
|
Scope: append(oidc.DefaultScope, "offline_access"),
|
2015-08-18 05:57:27 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
client, err := oidc.NewClient(ccfg)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Unable to create Client: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
client.SyncProviderConfig(*discovery)
|
|
|
|
|
|
|
|
hdlr := NewClientHandler(client, *discovery, *redirectURLParsed)
|
|
|
|
httpsrv := &http.Server{
|
|
|
|
Addr: fmt.Sprintf(":%s", p),
|
|
|
|
Handler: hdlr,
|
|
|
|
}
|
|
|
|
|
2016-06-09 02:23:26 +05:30
|
|
|
indexBytes, err := Asset("data/index.html")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("could not load template: %q", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
indexTemplate = template.Must(template.New("root").Parse(string(indexBytes)))
|
2016-06-03 17:21:00 +05:30
|
|
|
|
2016-06-09 02:23:26 +05:30
|
|
|
log.Infof("Binding to %s...", httpsrv.Addr)
|
2016-06-03 17:21:00 +05:30
|
|
|
if useTLS {
|
|
|
|
log.Info("Key and cert file provided. Using TLS")
|
|
|
|
log.Fatal(httpsrv.ListenAndServeTLS(*certFile, *keyFile))
|
|
|
|
} else {
|
|
|
|
log.Fatal(httpsrv.ListenAndServe())
|
|
|
|
}
|
2015-08-18 05:57:27 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
func NewClientHandler(c *oidc.Client, issuer string, cbURL url.URL) http.Handler {
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
|
2016-06-20 23:33:43 +05:30
|
|
|
oob := cbURL.String() == client.OOBRedirectURI
|
|
|
|
|
2015-08-18 05:57:27 +05:30
|
|
|
issuerURL, err := url.Parse(issuer)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Could not parse issuer url: %v", err)
|
|
|
|
}
|
|
|
|
|
2016-06-20 23:33:43 +05:30
|
|
|
mux.HandleFunc("/", handleIndexFunc(oob))
|
2015-08-18 05:57:27 +05:30
|
|
|
mux.HandleFunc("/login", handleLoginFunc(c))
|
|
|
|
mux.HandleFunc("/register", handleRegisterFunc(c))
|
2016-06-20 23:33:43 +05:30
|
|
|
if cbURL.String() != client.OOBRedirectURI {
|
|
|
|
mux.HandleFunc(cbURL.Path, handleCallbackFunc(c))
|
|
|
|
} else {
|
|
|
|
mux.HandleFunc("/callback", handleCallbackFunc(c))
|
|
|
|
}
|
2015-08-18 05:57:27 +05:30
|
|
|
|
|
|
|
resendURL := *issuerURL
|
2016-07-26 05:36:20 +05:30
|
|
|
resendURL.Path = path.Join(resendURL.Path, "/resend-verify-email")
|
2015-08-18 05:57:27 +05:30
|
|
|
|
|
|
|
mux.HandleFunc("/resend", handleResendFunc(c, *issuerURL, resendURL, cbURL))
|
|
|
|
return mux
|
|
|
|
}
|
|
|
|
|
2016-06-20 23:33:43 +05:30
|
|
|
func handleIndexFunc(oob bool) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err := indexTemplate.Execute(w, map[string]interface{}{
|
|
|
|
"OOB": oob,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
phttp.WriteError(w, http.StatusInternalServerError,
|
|
|
|
fmt.Sprintf("unable to execute template: %v", err))
|
2016-06-09 02:23:26 +05:30
|
|
|
|
2016-06-20 23:33:43 +05:30
|
|
|
}
|
2016-06-09 02:23:26 +05:30
|
|
|
}
|
2015-08-18 05:57:27 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
func handleLoginFunc(c *oidc.Client) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2016-06-09 02:23:26 +05:30
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
phttp.WriteError(w, http.StatusBadRequest,
|
|
|
|
fmt.Sprintf("Could not parse request: %v", err))
|
|
|
|
}
|
|
|
|
|
2015-08-18 05:57:27 +05:30
|
|
|
oac, err := c.OAuthClient()
|
|
|
|
if err != nil {
|
|
|
|
panic("unable to proceed")
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := url.Parse(oac.AuthCodeURL("", "", ""))
|
|
|
|
if err != nil {
|
|
|
|
panic("unable to proceed")
|
|
|
|
}
|
2016-06-09 02:23:26 +05:30
|
|
|
|
2016-07-16 04:31:12 +05:30
|
|
|
var scopes []string
|
|
|
|
q := u.Query()
|
|
|
|
if scope := q.Get("scope"); scope != "" {
|
|
|
|
scopes = strings.Split(scope, " ")
|
|
|
|
}
|
|
|
|
|
|
|
|
if xClient := r.Form.Get("cross_client"); xClient != "" {
|
2016-06-09 02:23:26 +05:30
|
|
|
xClients := strings.Split(xClient, ",")
|
2016-07-16 04:31:12 +05:30
|
|
|
for _, x := range xClients {
|
|
|
|
scopes = append(scopes, scope.ScopeGoogleCrossClient+x)
|
2016-06-09 02:23:26 +05:30
|
|
|
}
|
2016-07-16 04:31:12 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
if extraScopes := r.Form.Get("extra_scopes"); extraScopes != "" {
|
|
|
|
scopes = append(scopes, strings.Split(extraScopes, ",")...)
|
|
|
|
}
|
|
|
|
|
|
|
|
if scopes != nil {
|
|
|
|
q.Set("scope", strings.Join(scopes, " "))
|
2016-06-09 02:23:26 +05:30
|
|
|
u.RawQuery = q.Encode()
|
|
|
|
}
|
|
|
|
|
2015-08-18 05:57:27 +05:30
|
|
|
http.Redirect(w, r, u.String(), http.StatusFound)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleRegisterFunc(c *oidc.Client) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
oac, err := c.OAuthClient()
|
|
|
|
if err != nil {
|
|
|
|
panic("unable to proceed")
|
|
|
|
}
|
|
|
|
|
|
|
|
u, err := url.Parse(oac.AuthCodeURL("", "", ""))
|
|
|
|
q := u.Query()
|
|
|
|
q.Set("register", "1")
|
|
|
|
if err != nil {
|
|
|
|
panic("unable to proceed")
|
|
|
|
}
|
|
|
|
u.RawQuery = q.Encode()
|
|
|
|
log.Infof("URL: %v", u.String())
|
|
|
|
http.Redirect(w, r, u.String(), http.StatusFound)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleResendFunc(c *oidc.Client, issuerURL, resendURL, cbURL url.URL) http.HandlerFunc {
|
|
|
|
trans := &oidc.AuthenticatedTransport{
|
|
|
|
TokenRefresher: &oidc.ClientCredsTokenRefresher{
|
|
|
|
Issuer: issuerURL.String(),
|
|
|
|
OIDCClient: c,
|
|
|
|
},
|
|
|
|
RoundTripper: http.DefaultTransport,
|
|
|
|
}
|
|
|
|
hc := &http.Client{Transport: trans}
|
|
|
|
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
panic("unable to proceed")
|
|
|
|
}
|
|
|
|
|
|
|
|
tok := r.Form.Get("jwt")
|
|
|
|
q := struct {
|
|
|
|
Token string `json:"token"`
|
|
|
|
RedirectURI string `json:"redirectURI"`
|
|
|
|
}{
|
|
|
|
Token: tok,
|
|
|
|
RedirectURI: cbURL.String(),
|
|
|
|
}
|
|
|
|
qBytes, err := json.Marshal(&q)
|
|
|
|
res, err := hc.Post(resendURL.String(), "application/json", bytes.NewReader(qBytes))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("error requesting email resend:", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Write([]byte(fmt.Sprintf("Status from Dex: %v", res.Status)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleCallbackFunc(c *oidc.Client) http.HandlerFunc {
|
|
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
2016-07-16 04:31:12 +05:30
|
|
|
refreshToken := r.URL.Query().Get("refresh_token")
|
2015-08-18 05:57:27 +05:30
|
|
|
code := r.URL.Query().Get("code")
|
|
|
|
|
2016-07-16 04:31:12 +05:30
|
|
|
oac, err := c.OAuthClient()
|
2016-05-07 00:14:54 +05:30
|
|
|
if err != nil {
|
2016-07-16 04:31:12 +05:30
|
|
|
phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to create OAuth2 client: %v", err))
|
2016-05-07 00:14:54 +05:30
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-16 04:31:12 +05:30
|
|
|
var token oauth2.TokenResponse
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case code != "":
|
|
|
|
if token, err = oac.RequestToken(oauth2.GrantTypeAuthCode, code); err != nil {
|
|
|
|
phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to verify auth code with issuer: %v", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case refreshToken != "":
|
|
|
|
if token, err = oac.RequestToken(oauth2.GrantTypeRefreshToken, refreshToken); err != nil {
|
|
|
|
phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to refresh token: %v", err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if token.RefreshToken == "" {
|
|
|
|
token.RefreshToken = refreshToken
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
phttp.WriteError(w, http.StatusBadRequest, "code query param must be set")
|
2015-08-18 05:57:27 +05:30
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-16 04:31:12 +05:30
|
|
|
tok, err := jose.ParseJWT(token.IDToken)
|
2015-08-18 05:57:27 +05:30
|
|
|
if err != nil {
|
2016-07-16 04:31:12 +05:30
|
|
|
phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to parse JWT: %v", err))
|
2015-08-18 05:57:27 +05:30
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-16 04:31:12 +05:30
|
|
|
claims := new(bytes.Buffer)
|
|
|
|
if err := json.Indent(claims, tok.Payload, "", " "); err != nil {
|
|
|
|
phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to construct claims: %v", err))
|
|
|
|
return
|
|
|
|
}
|
2016-05-07 00:14:54 +05:30
|
|
|
s := fmt.Sprintf(`
|
|
|
|
<html>
|
2016-07-16 04:31:12 +05:30
|
|
|
<head>
|
|
|
|
<style>
|
|
|
|
/* make pre wrap */
|
|
|
|
pre {
|
|
|
|
white-space: pre-wrap; /* css-3 */
|
|
|
|
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
|
|
|
white-space: -pre-wrap; /* Opera 4-6 */
|
|
|
|
white-space: -o-pre-wrap; /* Opera 7 */
|
|
|
|
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
2016-05-07 00:14:54 +05:30
|
|
|
<body>
|
2016-07-16 04:31:12 +05:30
|
|
|
<p> Token: <pre><code>%v</code></pre></p>
|
|
|
|
<p> Claims: <pre><code>%v</code></pre></p>
|
|
|
|
<p> Refresh Token: <pre><code>%v</code></pre></p>
|
|
|
|
<p><a href="%s?refresh_token=%s">Redeem refresh token</a><p>
|
|
|
|
<p><a href="/resend?jwt=%s">Resend Verification Email</a></p>
|
2016-05-07 00:14:54 +05:30
|
|
|
</body>
|
2016-07-16 04:31:12 +05:30
|
|
|
</html>`, tok.Encode(), claims.String(), token.RefreshToken, r.URL.Path, token.RefreshToken, tok.Encode())
|
2015-08-18 05:57:27 +05:30
|
|
|
w.Write([]byte(s))
|
|
|
|
}
|
|
|
|
}
|