2017-12-14 21:41:38 +00:00
|
|
|
/*
|
|
|
|
Copyright 2017 Gravitational, Inc.
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
2019-02-06 02:12:40 +00:00
|
|
|
"context"
|
2020-10-13 16:14:46 +00:00
|
|
|
"net/url"
|
2022-01-12 14:55:25 +00:00
|
|
|
"testing"
|
2019-02-06 02:12:40 +00:00
|
|
|
"time"
|
|
|
|
|
2021-07-30 22:34:19 +00:00
|
|
|
"github.com/gravitational/teleport/api/types"
|
2019-02-06 02:12:40 +00:00
|
|
|
authority "github.com/gravitational/teleport/lib/auth/testauthority"
|
|
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
|
|
"github.com/gravitational/teleport/lib/backend/lite"
|
2020-10-13 16:14:46 +00:00
|
|
|
"github.com/gravitational/teleport/lib/events"
|
2022-03-25 22:21:06 +00:00
|
|
|
"github.com/gravitational/teleport/lib/events/eventstest"
|
2021-06-18 16:42:09 +00:00
|
|
|
"github.com/gravitational/teleport/lib/services"
|
2022-05-26 23:26:35 +00:00
|
|
|
|
2020-10-13 16:14:46 +00:00
|
|
|
"github.com/gravitational/trace"
|
2017-12-14 21:41:38 +00:00
|
|
|
|
2022-05-26 23:26:35 +00:00
|
|
|
"github.com/google/uuid"
|
2019-02-06 02:12:40 +00:00
|
|
|
"github.com/jonboulle/clockwork"
|
|
|
|
"gopkg.in/check.v1"
|
2017-12-14 21:41:38 +00:00
|
|
|
)
|
|
|
|
|
2022-01-12 14:55:25 +00:00
|
|
|
func TestAPI(t *testing.T) { check.TestingT(t) }
|
|
|
|
|
2019-02-06 02:12:40 +00:00
|
|
|
type GithubSuite struct {
|
2020-10-13 16:14:46 +00:00
|
|
|
a *Server
|
2022-03-25 22:21:06 +00:00
|
|
|
mockEmitter *eventstest.MockEmitter
|
2020-10-13 16:14:46 +00:00
|
|
|
b backend.Backend
|
|
|
|
c clockwork.FakeClock
|
2019-02-06 02:12:40 +00:00
|
|
|
}
|
2017-12-14 21:41:38 +00:00
|
|
|
|
|
|
|
var _ = check.Suite(&GithubSuite{})
|
|
|
|
|
|
|
|
func (s *GithubSuite) SetUpSuite(c *check.C) {
|
2019-02-06 02:12:40 +00:00
|
|
|
s.c = clockwork.NewFakeClockAt(time.Now())
|
|
|
|
|
2020-12-07 14:35:15 +00:00
|
|
|
var err error
|
2019-02-06 02:12:40 +00:00
|
|
|
s.b, err = lite.NewWithConfig(context.Background(), lite.Config{
|
|
|
|
Path: c.MkDir(),
|
|
|
|
PollStreamPeriod: 200 * time.Millisecond,
|
|
|
|
Clock: s.c,
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
2021-06-18 16:42:09 +00:00
|
|
|
clusterName, err := services.NewClusterNameWithRandomID(types.ClusterNameSpecV2{
|
2019-02-06 02:12:40 +00:00
|
|
|
ClusterName: "me.localhost",
|
|
|
|
})
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
authConfig := &InitConfig{
|
|
|
|
ClusterName: clusterName,
|
|
|
|
Backend: s.b,
|
|
|
|
Authority: authority.New(),
|
|
|
|
SkipPeriodicOperations: true,
|
|
|
|
}
|
2020-10-05 22:12:25 +00:00
|
|
|
s.a, err = NewServer(authConfig)
|
2019-02-06 02:12:40 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
2020-10-13 16:14:46 +00:00
|
|
|
|
2022-03-25 22:21:06 +00:00
|
|
|
s.mockEmitter = &eventstest.MockEmitter{}
|
2020-10-13 16:14:46 +00:00
|
|
|
s.a.emitter = s.mockEmitter
|
2017-12-14 21:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *GithubSuite) TestPopulateClaims(c *check.C) {
|
|
|
|
claims, err := populateGithubClaims(&testGithubAPIClient{})
|
|
|
|
c.Assert(err, check.IsNil)
|
2021-06-04 20:29:31 +00:00
|
|
|
c.Assert(claims, check.DeepEquals, &types.GithubClaims{
|
2017-12-15 02:07:20 +00:00
|
|
|
Username: "octocat",
|
2017-12-14 21:41:38 +00:00
|
|
|
OrganizationToTeams: map[string][]string{
|
2020-10-13 16:14:46 +00:00
|
|
|
"org1": {"team1", "team2"},
|
|
|
|
"org2": {"team1"},
|
2017-12-14 21:41:38 +00:00
|
|
|
},
|
2021-12-31 12:06:41 +00:00
|
|
|
Teams: []string{"team1", "team2", "team1"},
|
2017-12-14 21:41:38 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-02-06 02:12:40 +00:00
|
|
|
func (s *GithubSuite) TestCreateGithubUser(c *check.C) {
|
2022-05-26 23:26:35 +00:00
|
|
|
// Dry-run creation of Github user.
|
|
|
|
user, err := s.a.createGithubUser(context.Background(), &createUserParams{
|
|
|
|
connectorName: "github",
|
|
|
|
username: "foo@example.com",
|
|
|
|
roles: []string{"admin"},
|
|
|
|
sessionTTL: 1 * time.Minute,
|
|
|
|
}, true)
|
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
c.Assert(user.GetName(), check.Equals, "foo@example.com")
|
|
|
|
|
|
|
|
// Dry-run must not create a user.
|
|
|
|
_, err = s.a.GetUser("foo@example.com", false)
|
|
|
|
c.Assert(err, check.NotNil)
|
|
|
|
|
2019-02-06 02:12:40 +00:00
|
|
|
// Create GitHub user with 1 minute expiry.
|
2022-05-26 23:26:35 +00:00
|
|
|
_, err = s.a.createGithubUser(context.Background(), &createUserParams{
|
2019-02-19 01:42:26 +00:00
|
|
|
connectorName: "github",
|
|
|
|
username: "foo",
|
|
|
|
roles: []string{"admin"},
|
|
|
|
sessionTTL: 1 * time.Minute,
|
2022-05-26 23:26:35 +00:00
|
|
|
}, false)
|
2019-02-06 02:12:40 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Within that 1 minute period the user should still exist.
|
2019-08-29 23:16:03 +00:00
|
|
|
_, err = s.a.GetUser("foo", false)
|
2019-02-06 02:12:40 +00:00
|
|
|
c.Assert(err, check.IsNil)
|
|
|
|
|
|
|
|
// Advance time 2 minutes, the user should be gone.
|
|
|
|
s.c.Advance(2 * time.Minute)
|
2019-08-29 23:16:03 +00:00
|
|
|
_, err = s.a.GetUser("foo", false)
|
2019-02-06 02:12:40 +00:00
|
|
|
c.Assert(err, check.NotNil)
|
|
|
|
}
|
|
|
|
|
2017-12-14 21:41:38 +00:00
|
|
|
type testGithubAPIClient struct{}
|
|
|
|
|
2017-12-15 02:07:20 +00:00
|
|
|
func (c *testGithubAPIClient) getUser() (*userResponse, error) {
|
|
|
|
return &userResponse{Login: "octocat"}, nil
|
2017-12-14 21:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *testGithubAPIClient) getTeams() ([]teamResponse, error) {
|
|
|
|
return []teamResponse{
|
|
|
|
{
|
|
|
|
Name: "team1",
|
|
|
|
Slug: "team1",
|
|
|
|
Org: orgResponse{Login: "org1"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "team2",
|
|
|
|
Slug: "team2",
|
|
|
|
Org: orgResponse{Login: "org1"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "team1",
|
|
|
|
Slug: "team1",
|
|
|
|
Org: orgResponse{Login: "org2"},
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
2020-10-13 16:14:46 +00:00
|
|
|
|
|
|
|
func (s *GithubSuite) TestValidateGithubAuthCallbackEventsEmitted(c *check.C) {
|
2022-05-26 23:26:35 +00:00
|
|
|
auth := &GithubAuthResponse{
|
|
|
|
Username: "test-name",
|
|
|
|
}
|
|
|
|
|
|
|
|
claims := &types.GithubClaims{
|
|
|
|
OrganizationToTeams: map[string][]string{
|
2020-10-13 16:14:46 +00:00
|
|
|
"test": {},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-05-26 23:26:35 +00:00
|
|
|
ssoDiagInfoCalls := 0
|
|
|
|
|
2020-10-13 16:14:46 +00:00
|
|
|
m := &mockedGithubManager{}
|
2022-05-26 23:26:35 +00:00
|
|
|
m.createSSODiagnosticInfo = func(ctx context.Context, authKind string, authRequestID string, info types.SSODiagnosticInfo) error {
|
|
|
|
ssoDiagInfoCalls++
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestFlow: false
|
|
|
|
m.testFlow = false
|
2020-10-13 16:14:46 +00:00
|
|
|
|
|
|
|
// Test success event.
|
2022-05-26 23:26:35 +00:00
|
|
|
m.mockValidateGithubAuthCallback = func(ctx context.Context, diagCtx *ssoDiagContext, q url.Values) (*GithubAuthResponse, error) {
|
|
|
|
diagCtx.info.GithubClaims = claims
|
2020-10-13 16:14:46 +00:00
|
|
|
return auth, nil
|
|
|
|
}
|
|
|
|
_, _ = validateGithubAuthCallbackHelper(context.Background(), m, nil, s.a.emitter)
|
|
|
|
c.Assert(s.mockEmitter.LastEvent().GetType(), check.Equals, events.UserLoginEvent)
|
|
|
|
c.Assert(s.mockEmitter.LastEvent().GetCode(), check.Equals, events.UserSSOLoginCode)
|
2022-05-26 23:26:35 +00:00
|
|
|
c.Assert(ssoDiagInfoCalls, check.Equals, 0)
|
2020-10-13 16:14:46 +00:00
|
|
|
s.mockEmitter.Reset()
|
|
|
|
|
|
|
|
// Test failure event.
|
2022-05-26 23:26:35 +00:00
|
|
|
m.mockValidateGithubAuthCallback = func(ctx context.Context, diagCtx *ssoDiagContext, q url.Values) (*GithubAuthResponse, error) {
|
|
|
|
diagCtx.info.GithubClaims = claims
|
2020-10-13 16:14:46 +00:00
|
|
|
return auth, trace.BadParameter("")
|
|
|
|
}
|
|
|
|
_, _ = validateGithubAuthCallbackHelper(context.Background(), m, nil, s.a.emitter)
|
|
|
|
c.Assert(s.mockEmitter.LastEvent().GetCode(), check.Equals, events.UserSSOLoginFailureCode)
|
2022-05-26 23:26:35 +00:00
|
|
|
c.Assert(ssoDiagInfoCalls, check.Equals, 0)
|
|
|
|
|
|
|
|
// TestFlow: true
|
|
|
|
m.testFlow = true
|
|
|
|
|
|
|
|
// Test success event.
|
|
|
|
m.mockValidateGithubAuthCallback = func(ctx context.Context, diagCtx *ssoDiagContext, q url.Values) (*GithubAuthResponse, error) {
|
|
|
|
diagCtx.info.GithubClaims = claims
|
|
|
|
return auth, nil
|
|
|
|
}
|
|
|
|
_, _ = validateGithubAuthCallbackHelper(context.Background(), m, nil, s.a.emitter)
|
|
|
|
c.Assert(s.mockEmitter.LastEvent().GetType(), check.Equals, events.UserLoginEvent)
|
|
|
|
c.Assert(s.mockEmitter.LastEvent().GetCode(), check.Equals, events.UserSSOTestFlowLoginCode)
|
|
|
|
c.Assert(ssoDiagInfoCalls, check.Equals, 1)
|
|
|
|
s.mockEmitter.Reset()
|
|
|
|
|
|
|
|
// Test failure event.
|
|
|
|
m.mockValidateGithubAuthCallback = func(ctx context.Context, diagCtx *ssoDiagContext, q url.Values) (*GithubAuthResponse, error) {
|
|
|
|
diagCtx.info.GithubClaims = claims
|
|
|
|
return auth, trace.BadParameter("")
|
|
|
|
}
|
|
|
|
_, _ = validateGithubAuthCallbackHelper(context.Background(), m, nil, s.a.emitter)
|
|
|
|
c.Assert(s.mockEmitter.LastEvent().GetCode(), check.Equals, events.UserSSOTestFlowLoginFailureCode)
|
|
|
|
c.Assert(ssoDiagInfoCalls, check.Equals, 2)
|
2020-10-13 16:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type mockedGithubManager struct {
|
2022-05-26 23:26:35 +00:00
|
|
|
mockValidateGithubAuthCallback func(ctx context.Context, diagCtx *ssoDiagContext, q url.Values) (*GithubAuthResponse, error)
|
|
|
|
createSSODiagnosticInfo func(ctx context.Context, authKind string, authRequestID string, info types.SSODiagnosticInfo) error
|
|
|
|
|
|
|
|
testFlow bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockedGithubManager) newSSODiagContext(authKind string) *ssoDiagContext {
|
|
|
|
if m.createSSODiagnosticInfo == nil {
|
|
|
|
panic("mockedGithubManager.createSSODiagnosticInfo is nil, newSSODiagContext cannot succeed.")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &ssoDiagContext{
|
|
|
|
authKind: authKind,
|
|
|
|
createSSODiagnosticInfo: m.createSSODiagnosticInfo,
|
|
|
|
requestID: uuid.New().String(),
|
|
|
|
info: types.SSODiagnosticInfo{TestFlow: m.testFlow},
|
|
|
|
}
|
2020-10-13 16:14:46 +00:00
|
|
|
}
|
|
|
|
|
2022-05-26 23:26:35 +00:00
|
|
|
func (m *mockedGithubManager) validateGithubAuthCallback(ctx context.Context, diagCtx *ssoDiagContext, q url.Values) (*GithubAuthResponse, error) {
|
2020-10-13 16:14:46 +00:00
|
|
|
if m.mockValidateGithubAuthCallback != nil {
|
2022-05-26 23:26:35 +00:00
|
|
|
return m.mockValidateGithubAuthCallback(ctx, diagCtx, q)
|
2020-10-13 16:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil, trace.NotImplemented("mockValidateGithubAuthCallback not implemented")
|
|
|
|
}
|