Skip to content

Commit

Permalink
Merge pull request #5 from samsarahq/stephen/allowed
Browse files Browse the repository at this point in the history
Support custom redirects
  • Loading branch information
stephen authored Oct 29, 2018
2 parents adb695e + 8427c4a commit fda163f
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 60 deletions.
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ func main() {
flagSet.String("validate-url", "", "Access token validation endpoint")
flagSet.String("scope", "", "OAuth scope specification")
flagSet.String("approval-prompt", "force", "OAuth approval_prompt")
flagSet.String("allowed-url", "", "Regexp for allowed redirect URLs")
flagSet.Bool("use-javascript-redirect", false, "Use javascript on the signin page to set the redirect url")

flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")

Expand Down
145 changes: 85 additions & 60 deletions oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,27 @@ type OAuthProxy struct {
OAuthCallbackPath string
AuthOnlyPath string

redirectURL *url.URL // the url to receive requests at
provider providers.Provider
ProxyPrefix string
SignInMessage string
HtpasswdFile *HtpasswdFile
DisplayHtpasswdForm bool
serveMux http.Handler
SetXAuthRequest bool
PassBasicAuth bool
SkipProviderButton bool
PassUserHeaders bool
BasicAuthPassword string
PassAccessToken bool
CookieCipher *cookie.Cipher
skipAuthRegex []string
skipAuthPreflight bool
compiledRegex []*regexp.Regexp
templates *template.Template
Footer string
redirectURL *url.URL // the url to receive requests at
allowedURL string
UseJavascriptRedirect bool
provider providers.Provider
ProxyPrefix string
SignInMessage string
HtpasswdFile *HtpasswdFile
DisplayHtpasswdForm bool
serveMux http.Handler
SetXAuthRequest bool
PassBasicAuth bool
SkipProviderButton bool
PassUserHeaders bool
BasicAuthPassword string
PassAccessToken bool
CookieCipher *cookie.Cipher
skipAuthRegex []string
skipAuthPreflight bool
compiledRegex []*regexp.Regexp
templates *template.Template
Footer string
}

type UpstreamProxy struct {
Expand Down Expand Up @@ -229,22 +231,24 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix),
AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix),

ProxyPrefix: opts.ProxyPrefix,
provider: opts.provider,
serveMux: serveMux,
redirectURL: redirectURL,
skipAuthRegex: opts.SkipAuthRegex,
skipAuthPreflight: opts.SkipAuthPreflight,
compiledRegex: opts.CompiledRegex,
SetXAuthRequest: opts.SetXAuthRequest,
PassBasicAuth: opts.PassBasicAuth,
PassUserHeaders: opts.PassUserHeaders,
BasicAuthPassword: opts.BasicAuthPassword,
PassAccessToken: opts.PassAccessToken,
SkipProviderButton: opts.SkipProviderButton,
CookieCipher: cipher,
templates: loadTemplates(opts.CustomTemplatesDir),
Footer: opts.Footer,
ProxyPrefix: opts.ProxyPrefix,
provider: opts.provider,
serveMux: serveMux,
redirectURL: redirectURL,
allowedURL: opts.AllowedURL,
skipAuthRegex: opts.SkipAuthRegex,
skipAuthPreflight: opts.SkipAuthPreflight,
compiledRegex: opts.CompiledRegex,
SetXAuthRequest: opts.SetXAuthRequest,
PassBasicAuth: opts.PassBasicAuth,
PassUserHeaders: opts.PassUserHeaders,
BasicAuthPassword: opts.BasicAuthPassword,
PassAccessToken: opts.PassAccessToken,
SkipProviderButton: opts.SkipProviderButton,
CookieCipher: cipher,
templates: loadTemplates(opts.CustomTemplatesDir),
Footer: opts.Footer,
UseJavascriptRedirect: opts.UseJavascriptRedirect,
}
}

Expand Down Expand Up @@ -422,21 +426,23 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
}

t := struct {
ProviderName string
SignInMessage string
CustomLogin bool
Redirect string
Version string
ProxyPrefix string
Footer template.HTML
ProviderName string
SignInMessage string
CustomLogin bool
Redirect string
Version string
ProxyPrefix string
Footer template.HTML
UseJavascriptRedirect bool
}{
ProviderName: p.provider.Data().ProviderName,
SignInMessage: p.SignInMessage,
CustomLogin: p.displayCustomLoginForm(),
Redirect: redirect_url,
Version: VERSION,
ProxyPrefix: p.ProxyPrefix,
Footer: template.HTML(p.Footer),
ProviderName: p.provider.Data().ProviderName,
SignInMessage: p.SignInMessage,
CustomLogin: p.displayCustomLoginForm(),
Redirect: redirect_url,
Version: VERSION,
ProxyPrefix: p.ProxyPrefix,
Footer: template.HTML(p.Footer),
UseJavascriptRedirect: p.UseJavascriptRedirect,
}
p.templates.ExecuteTemplate(rw, "sign_in.html", t)
}
Expand All @@ -463,12 +469,11 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
if err != nil {
return
}

redirect = req.Form.Get("rd")
if redirect == "" || !strings.HasPrefix(redirect, "/") || strings.HasPrefix(redirect, "//") {
redirect = "/"
redirect, err = p.getValidatedRedirect(req.Form.Get("rd"))
if err != nil {
log.Printf("failed to validate redirect %s", err)
return redirect, err
}

return
}

Expand Down Expand Up @@ -539,6 +544,26 @@ func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
}
}

func (p *OAuthProxy) getValidatedRedirect(redirect string) (string, error) {
fallbackRedirect := "/"
// We using 2 types of validation - basic checks or based on allowedURLs
switch {
case redirect == "":
return fallbackRedirect, nil
case p.allowedURL != "":
matched, err := regexp.MatchString(p.allowedURL, redirect)
if err != nil {
return fallbackRedirect, err
}
if !matched {
return fallbackRedirect, err
}
case !strings.HasPrefix(redirect, "/") || strings.HasPrefix(redirect, "//"):
return fallbackRedirect, nil
}
return redirect, nil
}

func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
p.ClearSessionCookie(rw, req)
http.Redirect(rw, req, "/", 302)
Expand Down Expand Up @@ -588,7 +613,12 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
return
}
nonce := s[0]
redirect := s[1]
redirect, err := p.getValidatedRedirect(s[1])
if err != nil {
log.Printf("failed to validate redirect %s", err)
p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
return
}
c, err := req.Cookie(p.CSRFCookieName)
if err != nil {
p.ErrorPage(rw, 403, "Permission Denied", err.Error())
Expand All @@ -600,11 +630,6 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
p.ErrorPage(rw, 403, "Permission Denied", "csrf failed")
return
}

if !strings.HasPrefix(redirect, "/") || strings.HasPrefix(redirect, "//") {
redirect = "/"
}

// set cookie, or deny
if p.Validator(session.Email) && p.provider.ValidateGroup(session.Email) {
log.Printf("%s authentication complete %s", remoteAddr, session)
Expand Down
46 changes: 46 additions & 0 deletions oauthproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"testing"
"time"

"github.com/bitly/oauth2_proxy/providers"
"github.com/bmizerany/assert"
"github.com/mbland/hmacauth"
"github.com/samsarahq/oauth2_proxy/providers"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -131,6 +133,50 @@ func (tp *TestProvider) ValidateSessionState(session *providers.SessionState) bo
return tp.ValidToken
}

func TestGetValidatedRedirect(t *testing.T) {
opts := NewOptions()
opts.ClientID = "bazquux"
opts.ClientSecret = "foobar"
opts.CookieSecret = "xyzzyplugh"

opts.AllowedURL = ".+\\.internals\\.example\\.com"
opts.Validate()

proxy := NewOAuthProxy(opts, func(string) bool { return true })
fallbackRedirect := "/"

noRD, err := proxy.getValidatedRedirect("")
assert.Equal(t, nil, err)
assert.Equal(t, fallbackRedirect, noRD)

singleSlash, err := proxy.getValidatedRedirect("/redirect")
assert.Equal(t, nil, err)
assert.Equal(t, singleSlash, singleSlash)

doubleSlash, err := proxy.getValidatedRedirect("//redirect")
assert.Equal(t, nil, err)
assert.Equal(t, fallbackRedirect, doubleSlash)

validHttp, err := proxy.getValidatedRedirect("http://internals.example.com/redirect")
assert.Equal(t, nil, err)
assert.Equal(t, validHttp, validHttp)

validHttps, err := proxy.getValidatedRedirect("https://internals.example.com/redirect")
assert.Equal(t, nil, err)
assert.Equal(t, validHttps, validHttps)

invalidHttp, err := proxy.getValidatedRedirect("http://internals.corporate.com/redirect")
assert.Equal(t, nil, err)
assert.Equal(t, fallbackRedirect, invalidHttp)

// Test for incorrect regexp
opts.AllowedURL = "*"
opts.Validate()
proxy = NewOAuthProxy(opts, func(string) bool { return true })
_, hasErr := proxy.getValidatedRedirect("http://internals.corporate.com/redirect")
assert.NotEqual(t, nil, hasErr)
}

func TestBasicAuthPassword(t *testing.T) {
provider_server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%#v", r)
Expand Down
2 changes: 2 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ type Options struct {
DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"`
CustomTemplatesDir string `flag:"custom-templates-dir" cfg:"custom_templates_dir"`
Footer string `flag:"footer" cfg:"footer"`
AllowedURL string `flag:"allowed-url" cfg:"allowed-url"`
UseJavascriptRedirect bool `flag:"use-javascript-redirect" cfg:"use-javascript-redirect"`

CookieName string `flag:"cookie-name" cfg:"cookie_name" env:"OAUTH2_PROXY_COOKIE_NAME"`
CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"OAUTH2_PROXY_COOKIE_SECRET"`
Expand Down
6 changes: 6 additions & 0 deletions templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ func getTemplates() *template.Template {
})();
}
</script>
{{if .UseJavascriptRedirect}}
<script>
var selectors = Array.prototype.slice.call(document.querySelectorAll('input[name="rd"]'));
selectors.forEach((input) => input.value = window.location.href);
</script>
{{end}}
<footer>
{{ if eq .Footer "-" }}
{{ else if eq .Footer ""}}
Expand Down

0 comments on commit fda163f

Please sign in to comment.