Skip to content

Commit

Permalink
Limit allowed redirect URLs from GetAuthorizationURL (#5282)
Browse files Browse the repository at this point in the history
  • Loading branch information
evankanderson authored Jan 10, 2025
1 parent 60882e1 commit f2e2365
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 0 deletions.
39 changes: 39 additions & 0 deletions internal/controlplane/handlers_oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ func (s *Server) GetAuthorizationURL(ctx context.Context,
entityCtx := engcontext.EntityFromContext(ctx)
projectID := entityCtx.Project.ID

redirectUrl, err := url.Parse(req.GetRedirectUrl())
if err != nil || !s.alllowedRedirectURL(redirectUrl) {
return nil, util.UserVisibleError(codes.InvalidArgument, "invalid redirect URL")
}

var providerName string
if req.GetContext().GetProvider() == "" {
providerName = defaultProvider
Expand Down Expand Up @@ -186,6 +191,40 @@ func (s *Server) GetAuthorizationURL(ctx context.Context,
}, nil
}

func (s *Server) alllowedRedirectURL(redirectUrl *url.URL) bool {
if redirectUrl == nil || redirectUrl.String() == "" {
return true // Empty URL is allowed
}
if redirectUrl.Host == "localhost" {
return true
}
hostUrl, err := redirectUrl.Parse("/")
if err != nil {
return false
}
hostUrlString := hostUrl.String()

providerCfg := s.cfg.Provider

if providerCfg.GitHub != nil && strings.HasPrefix(providerCfg.GitHub.RedirectURI, hostUrlString) {
return true
}
if providerCfg.GitHubApp != nil && strings.HasPrefix(providerCfg.GitHubApp.RedirectURI, hostUrlString) {
return true
}
if providerCfg.GitLab != nil && strings.HasPrefix(providerCfg.GitLab.RedirectURI, hostUrlString) {
return true
}

if slices.ContainsFunc(s.cfg.HTTPServer.CORS.AllowOrigins, func(u string) bool {
return u == hostUrlString || u+"/" == hostUrlString
}) {
return true
}

return false
}

// HandleOAuthCallback handles the OAuth 2.0 authorization code callback from the enrolled
// provider. This function gathers the state from the database and compares it to the state
// passed in. If they match, the provider code is exchanged for a provider token.
Expand Down
22 changes: 22 additions & 0 deletions internal/controlplane/handlers_oauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ func TestGetAuthorizationURL(t *testing.T) {
githubAppProviderClass := "github-app"
nonGithubProviderName := "non-github"
projectIdStr := projectID.String()
attackerUrl := "https://www.attacker.com/collect/here"

testCases := []struct {
name string
Expand Down Expand Up @@ -231,6 +232,7 @@ func TestGetAuthorizationURL(t *testing.T) {

if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}

if res.Url == "" {
Expand Down Expand Up @@ -265,6 +267,7 @@ func TestGetAuthorizationURL(t *testing.T) {

if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}

if res.Url == "" {
Expand Down Expand Up @@ -293,6 +296,24 @@ func TestGetAuthorizationURL(t *testing.T) {

expectedStatusCode: codes.InvalidArgument,
},
{
name: "Bad redirect URL",
req: &pb.GetAuthorizationURLRequest{
Context: &pb.Context{
Provider: &githubProviderClass,
Project: &projectIdStr,
},
Cli: true,
RedirectUrl: &attackerUrl,
},
buildStubs: func(_ *mockdb.MockStore) {},
checkResponse: func(t *testing.T, _ *pb.GetAuthorizationURLResponse, err error) {
t.Helper()

assert.Error(t, err, "Expected error in GetAuthorizationURL")
},
expectedStatusCode: codes.InvalidArgument,
},
{
name: "No GitHub id",
req: &pb.GetAuthorizationURLRequest{
Expand Down Expand Up @@ -325,6 +346,7 @@ func TestGetAuthorizationURL(t *testing.T) {

if err != nil {
t.Errorf("Unexpected error: %v", err)
return
}

if res.Url == "" {
Expand Down

0 comments on commit f2e2365

Please sign in to comment.