2022-04-23 22:58:57 +00:00
|
|
|
package coderd_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-08-01 04:05:35 +00:00
|
|
|
"crypto"
|
2022-10-18 03:07:11 +00:00
|
|
|
"fmt"
|
2022-08-01 04:05:35 +00:00
|
|
|
"io"
|
2022-04-23 22:58:57 +00:00
|
|
|
"net/http"
|
2022-10-18 03:07:11 +00:00
|
|
|
"strings"
|
2022-04-23 22:58:57 +00:00
|
|
|
"testing"
|
|
|
|
|
2022-08-01 04:05:35 +00:00
|
|
|
"github.com/coreos/go-oidc/v3/oidc"
|
|
|
|
"github.com/golang-jwt/jwt"
|
2022-04-23 22:58:57 +00:00
|
|
|
"github.com/google/go-github/v43/github"
|
2023-03-22 19:52:13 +00:00
|
|
|
"github.com/google/uuid"
|
2022-08-01 04:05:35 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2022-04-23 22:58:57 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"golang.org/x/oauth2"
|
2022-07-13 00:45:43 +00:00
|
|
|
"golang.org/x/xerrors"
|
2022-04-23 22:58:57 +00:00
|
|
|
|
|
|
|
"github.com/coder/coder/coderd"
|
2023-02-06 20:12:50 +00:00
|
|
|
"github.com/coder/coder/coderd/audit"
|
2022-04-23 22:58:57 +00:00
|
|
|
"github.com/coder/coder/coderd/coderdtest"
|
2022-10-25 00:46:24 +00:00
|
|
|
"github.com/coder/coder/coderd/database"
|
2022-04-23 22:58:57 +00:00
|
|
|
"github.com/coder/coder/codersdk"
|
2022-08-09 17:17:00 +00:00
|
|
|
"github.com/coder/coder/testutil"
|
2022-04-23 22:58:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestUserAuthMethods(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
t.Run("Password", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, nil)
|
2022-08-09 17:17:00 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
methods, err := client.AuthMethods(ctx)
|
2022-04-23 22:58:57 +00:00
|
|
|
require.NoError(t, err)
|
2023-01-31 18:33:25 +00:00
|
|
|
require.True(t, methods.Password.Enabled)
|
|
|
|
require.False(t, methods.Github.Enabled)
|
2022-04-23 22:58:57 +00:00
|
|
|
})
|
|
|
|
t.Run("Github", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{},
|
|
|
|
})
|
2022-08-09 17:17:00 +00:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
methods, err := client.AuthMethods(ctx)
|
2022-04-23 22:58:57 +00:00
|
|
|
require.NoError(t, err)
|
2023-01-31 18:33:25 +00:00
|
|
|
require.True(t, methods.Password.Enabled)
|
|
|
|
require.True(t, methods.Github.Enabled)
|
2022-04-23 22:58:57 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-08-21 22:32:53 +00:00
|
|
|
// nolint:bodyclose
|
2022-04-23 22:58:57 +00:00
|
|
|
func TestUserOAuth2Github(t *testing.T) {
|
|
|
|
t.Parallel()
|
2022-10-07 16:53:58 +00:00
|
|
|
|
|
|
|
stateActive := "active"
|
|
|
|
statePending := "pending"
|
|
|
|
|
2022-04-23 22:58:57 +00:00
|
|
|
t.Run("NotInAllowedOrganization", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-04-23 22:58:57 +00:00
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{{
|
2022-10-07 16:53:58 +00:00
|
|
|
State: &stateActive,
|
2022-04-23 22:58:57 +00:00
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("kyle"),
|
|
|
|
},
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
resp := oauth2Callback(t, client)
|
|
|
|
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
|
|
|
})
|
2022-07-09 02:37:18 +00:00
|
|
|
t.Run("NotInAllowedTeam", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
|
|
|
AllowOrganizations: []string{"coder"},
|
|
|
|
AllowTeams: []coderd.GithubOAuth2Team{{"another", "something"}, {"coder", "frontend"}},
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-07-09 02:37:18 +00:00
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{{
|
2022-10-07 16:53:58 +00:00
|
|
|
State: &stateActive,
|
2022-07-09 02:37:18 +00:00
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("coder"),
|
|
|
|
},
|
|
|
|
}}, nil
|
|
|
|
},
|
2022-07-22 18:54:08 +00:00
|
|
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
|
|
|
return &github.User{
|
|
|
|
Login: github.String("kyle"),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
TeamMembership: func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error) {
|
2022-07-13 00:45:43 +00:00
|
|
|
return nil, xerrors.New("no perms")
|
2022-07-09 02:37:18 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-07-09 02:37:18 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
|
|
|
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
|
|
|
})
|
2022-04-23 22:58:57 +00:00
|
|
|
t.Run("UnverifiedEmail", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-04-23 22:58:57 +00:00
|
|
|
AllowOrganizations: []string{"coder"},
|
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{{
|
2022-10-07 16:53:58 +00:00
|
|
|
State: &stateActive,
|
2022-04-23 22:58:57 +00:00
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("coder"),
|
|
|
|
},
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
|
|
|
return &github.User{}, nil
|
|
|
|
},
|
|
|
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
|
|
|
return []*github.UserEmail{{
|
|
|
|
Email: github.String("testuser@coder.com"),
|
|
|
|
Verified: github.Bool(false),
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-04-23 22:58:57 +00:00
|
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-04-23 22:58:57 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2023-01-13 14:30:48 +00:00
|
|
|
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
2022-04-23 22:58:57 +00:00
|
|
|
})
|
|
|
|
t.Run("BlockSignups", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-04-23 22:58:57 +00:00
|
|
|
AllowOrganizations: []string{"coder"},
|
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{{
|
2022-10-07 16:53:58 +00:00
|
|
|
State: &stateActive,
|
2022-04-23 22:58:57 +00:00
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("coder"),
|
|
|
|
},
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
|
|
|
return &github.User{}, nil
|
|
|
|
},
|
|
|
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
2022-08-22 23:13:46 +00:00
|
|
|
return []*github.UserEmail{{
|
|
|
|
Email: github.String("testuser@coder.com"),
|
|
|
|
Verified: github.Bool(true),
|
|
|
|
Primary: github.Bool(true),
|
|
|
|
}}, nil
|
2022-04-23 22:58:57 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-04-23 22:58:57 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-04-23 22:58:57 +00:00
|
|
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
|
|
|
})
|
2022-08-17 23:00:53 +00:00
|
|
|
t.Run("MultiLoginNotAllowed", func(t *testing.T) {
|
2022-04-23 22:58:57 +00:00
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-04-23 22:58:57 +00:00
|
|
|
AllowOrganizations: []string{"coder"},
|
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{{
|
2022-10-07 16:53:58 +00:00
|
|
|
State: &stateActive,
|
2022-04-23 22:58:57 +00:00
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("coder"),
|
|
|
|
},
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
2022-08-17 23:00:53 +00:00
|
|
|
return &github.User{}, nil
|
2022-04-23 22:58:57 +00:00
|
|
|
},
|
|
|
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
2022-04-29 20:13:35 +00:00
|
|
|
return []*github.UserEmail{{
|
2022-08-17 23:00:53 +00:00
|
|
|
Email: github.String("testuser@coder.com"),
|
2022-04-29 20:13:35 +00:00
|
|
|
Verified: github.Bool(true),
|
2022-08-22 23:13:46 +00:00
|
|
|
Primary: github.Bool(true),
|
2022-04-29 20:13:35 +00:00
|
|
|
}}, nil
|
2022-04-23 22:58:57 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-08-17 23:00:53 +00:00
|
|
|
// Creates the first user with login_type 'password'.
|
|
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-08-17 23:00:53 +00:00
|
|
|
// Attempting to login should give us a 403 since the user
|
|
|
|
// already has a login_type of 'password'.
|
2022-04-23 22:58:57 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-08-17 23:00:53 +00:00
|
|
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
2022-04-23 22:58:57 +00:00
|
|
|
})
|
2022-08-17 23:00:53 +00:00
|
|
|
t.Run("Signup", func(t *testing.T) {
|
2022-04-23 22:58:57 +00:00
|
|
|
t.Parallel()
|
2023-02-06 20:12:50 +00:00
|
|
|
auditor := audit.NewMock()
|
2022-04-23 22:58:57 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2023-02-06 20:12:50 +00:00
|
|
|
Auditor: auditor,
|
2022-04-23 22:58:57 +00:00
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-04-23 22:58:57 +00:00
|
|
|
AllowOrganizations: []string{"coder"},
|
2022-08-17 23:00:53 +00:00
|
|
|
AllowSignups: true,
|
2022-04-23 22:58:57 +00:00
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{{
|
2022-10-07 16:53:58 +00:00
|
|
|
State: &stateActive,
|
2022-04-23 22:58:57 +00:00
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("coder"),
|
|
|
|
},
|
|
|
|
}}, nil
|
|
|
|
},
|
2022-09-04 16:44:27 +00:00
|
|
|
AuthenticatedUser: func(ctx context.Context, _ *http.Client) (*github.User, error) {
|
2022-08-17 23:00:53 +00:00
|
|
|
return &github.User{
|
2022-09-04 16:44:27 +00:00
|
|
|
Login: github.String("kyle"),
|
|
|
|
ID: i64ptr(1234),
|
|
|
|
AvatarURL: github.String("/hello-world"),
|
2022-08-17 23:00:53 +00:00
|
|
|
}, nil
|
2022-04-23 22:58:57 +00:00
|
|
|
},
|
|
|
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
|
|
|
return []*github.UserEmail{{
|
2022-08-17 23:00:53 +00:00
|
|
|
Email: github.String("kyle@coder.com"),
|
2022-04-23 22:58:57 +00:00
|
|
|
Verified: github.Bool(true),
|
2022-08-17 23:00:53 +00:00
|
|
|
Primary: github.Bool(true),
|
2022-04-23 22:58:57 +00:00
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-03-30 17:13:03 +00:00
|
|
|
numLogs := len(auditor.AuditLogs())
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-04-23 22:58:57 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
numLogs++ // add an audit log for login
|
|
|
|
|
2022-04-23 22:58:57 +00:00
|
|
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
2022-08-17 23:00:53 +00:00
|
|
|
|
2022-11-09 13:31:24 +00:00
|
|
|
client.SetSessionToken(authCookieValue(resp.Cookies()))
|
2022-08-17 23:00:53 +00:00
|
|
|
user, err := client.User(context.Background(), "me")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "kyle@coder.com", user.Email)
|
|
|
|
require.Equal(t, "kyle", user.Username)
|
2022-09-04 16:44:27 +00:00
|
|
|
require.Equal(t, "/hello-world", user.AvatarURL)
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2023-03-30 17:13:03 +00:00
|
|
|
require.Len(t, auditor.AuditLogs(), numLogs)
|
|
|
|
require.NotEqual(t, auditor.AuditLogs()[numLogs-1].UserID, uuid.Nil)
|
|
|
|
require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)
|
2022-04-23 22:58:57 +00:00
|
|
|
})
|
2022-07-09 02:37:18 +00:00
|
|
|
t.Run("SignupAllowedTeam", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2023-02-06 20:12:50 +00:00
|
|
|
auditor := audit.NewMock()
|
2022-07-09 02:37:18 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2023-02-06 20:12:50 +00:00
|
|
|
Auditor: auditor,
|
2022-07-09 02:37:18 +00:00
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
|
|
|
AllowSignups: true,
|
|
|
|
AllowOrganizations: []string{"coder"},
|
|
|
|
AllowTeams: []coderd.GithubOAuth2Team{{"coder", "frontend"}},
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-07-09 02:37:18 +00:00
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{{
|
2022-10-07 16:53:58 +00:00
|
|
|
State: &stateActive,
|
2022-07-09 02:37:18 +00:00
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("coder"),
|
|
|
|
},
|
|
|
|
}}, nil
|
|
|
|
},
|
2022-07-22 18:54:08 +00:00
|
|
|
TeamMembership: func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error) {
|
|
|
|
return &github.Membership{}, nil
|
2022-07-09 02:37:18 +00:00
|
|
|
},
|
|
|
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
|
|
|
return &github.User{
|
|
|
|
Login: github.String("kyle"),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
|
|
|
return []*github.UserEmail{{
|
|
|
|
Email: github.String("kyle@coder.com"),
|
|
|
|
Verified: github.Bool(true),
|
|
|
|
Primary: github.Bool(true),
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-03-30 17:13:03 +00:00
|
|
|
numLogs := len(auditor.AuditLogs())
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-07-09 02:37:18 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
numLogs++ // add an audit log for login
|
|
|
|
|
2022-07-09 02:37:18 +00:00
|
|
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
2023-03-30 17:13:03 +00:00
|
|
|
require.Len(t, auditor.AuditLogs(), numLogs)
|
|
|
|
require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)
|
2022-07-09 02:37:18 +00:00
|
|
|
})
|
2022-11-15 16:56:46 +00:00
|
|
|
t.Run("SignupAllowedTeamInFirstOrganization", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2023-02-06 20:12:50 +00:00
|
|
|
auditor := audit.NewMock()
|
2022-11-15 16:56:46 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2023-02-06 20:12:50 +00:00
|
|
|
Auditor: auditor,
|
2022-11-15 16:56:46 +00:00
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
|
|
|
AllowSignups: true,
|
|
|
|
AllowOrganizations: []string{"coder", "nil"},
|
|
|
|
AllowTeams: []coderd.GithubOAuth2Team{{"coder", "backend"}},
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-11-15 16:56:46 +00:00
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{
|
|
|
|
{
|
|
|
|
State: &stateActive,
|
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("coder"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
State: &stateActive,
|
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("nil"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
TeamMembership: func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error) {
|
|
|
|
return &github.Membership{}, nil
|
|
|
|
},
|
|
|
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
|
|
|
return &github.User{
|
|
|
|
Login: github.String("mathias"),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
|
|
|
return []*github.UserEmail{{
|
|
|
|
Email: github.String("mathias@coder.com"),
|
|
|
|
Verified: github.Bool(true),
|
|
|
|
Primary: github.Bool(true),
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-03-30 17:13:03 +00:00
|
|
|
numLogs := len(auditor.AuditLogs())
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-11-15 16:56:46 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
numLogs++ // add an audit log for login
|
|
|
|
|
2022-11-15 16:56:46 +00:00
|
|
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
2023-03-30 17:13:03 +00:00
|
|
|
require.Len(t, auditor.AuditLogs(), numLogs)
|
|
|
|
require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)
|
2022-11-15 16:56:46 +00:00
|
|
|
})
|
|
|
|
t.Run("SignupAllowedTeamInSecondOrganization", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2023-02-06 20:12:50 +00:00
|
|
|
auditor := audit.NewMock()
|
2022-11-15 16:56:46 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2023-02-06 20:12:50 +00:00
|
|
|
Auditor: auditor,
|
2022-11-15 16:56:46 +00:00
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
|
|
|
AllowSignups: true,
|
|
|
|
AllowOrganizations: []string{"coder", "nil"},
|
|
|
|
AllowTeams: []coderd.GithubOAuth2Team{{"nil", "null"}},
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-11-15 16:56:46 +00:00
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{
|
|
|
|
{
|
|
|
|
State: &stateActive,
|
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("coder"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
State: &stateActive,
|
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("nil"),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
TeamMembership: func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error) {
|
|
|
|
return &github.Membership{}, nil
|
|
|
|
},
|
|
|
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
|
|
|
return &github.User{
|
|
|
|
Login: github.String("mathias"),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
|
|
|
return []*github.UserEmail{{
|
|
|
|
Email: github.String("mathias@coder.com"),
|
|
|
|
Verified: github.Bool(true),
|
|
|
|
Primary: github.Bool(true),
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-03-30 17:13:03 +00:00
|
|
|
numLogs := len(auditor.AuditLogs())
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-11-15 16:56:46 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
numLogs++ // add an audit log for login
|
|
|
|
|
2022-11-15 16:56:46 +00:00
|
|
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
2023-03-30 17:13:03 +00:00
|
|
|
require.Len(t, auditor.AuditLogs(), numLogs)
|
|
|
|
require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)
|
2022-11-15 16:56:46 +00:00
|
|
|
})
|
|
|
|
t.Run("SignupAllowEveryone", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2023-02-06 20:12:50 +00:00
|
|
|
auditor := audit.NewMock()
|
2022-11-15 16:56:46 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2023-02-06 20:12:50 +00:00
|
|
|
Auditor: auditor,
|
2022-11-15 16:56:46 +00:00
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
|
|
|
AllowSignups: true,
|
|
|
|
AllowEveryone: true,
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-11-15 16:56:46 +00:00
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{}, nil
|
|
|
|
},
|
|
|
|
TeamMembership: func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error) {
|
|
|
|
return nil, xerrors.New("no teams")
|
|
|
|
},
|
|
|
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
|
|
|
return &github.User{
|
|
|
|
Login: github.String("mathias"),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
|
|
|
return []*github.UserEmail{{
|
|
|
|
Email: github.String("mathias@coder.com"),
|
|
|
|
Verified: github.Bool(true),
|
|
|
|
Primary: github.Bool(true),
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-03-30 17:13:03 +00:00
|
|
|
numLogs := len(auditor.AuditLogs())
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-11-15 16:56:46 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
numLogs++ // add an audit log for login
|
|
|
|
|
2022-11-15 16:56:46 +00:00
|
|
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
2023-03-30 17:13:03 +00:00
|
|
|
require.Len(t, auditor.AuditLogs(), numLogs)
|
|
|
|
require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)
|
2022-11-15 16:56:46 +00:00
|
|
|
})
|
2022-10-07 16:53:58 +00:00
|
|
|
t.Run("SignupFailedInactiveInOrg", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
|
|
|
AllowSignups: true,
|
|
|
|
AllowOrganizations: []string{"coder"},
|
|
|
|
AllowTeams: []coderd.GithubOAuth2Team{{"coder", "frontend"}},
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-10-07 16:53:58 +00:00
|
|
|
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
|
|
|
return []*github.Membership{{
|
|
|
|
State: &statePending,
|
|
|
|
Organization: &github.Organization{
|
|
|
|
Login: github.String("coder"),
|
|
|
|
},
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
TeamMembership: func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error) {
|
|
|
|
return &github.Membership{}, nil
|
|
|
|
},
|
|
|
|
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
|
|
|
return &github.User{
|
|
|
|
Login: github.String("kyle"),
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
|
|
|
return []*github.UserEmail{{
|
|
|
|
Email: github.String("kyle@coder.com"),
|
|
|
|
Verified: github.Bool(true),
|
|
|
|
Primary: github.Bool(true),
|
|
|
|
}}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-10-07 16:53:58 +00:00
|
|
|
resp := oauth2Callback(t, client)
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-10-07 16:53:58 +00:00
|
|
|
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
|
|
|
})
|
2022-04-23 22:58:57 +00:00
|
|
|
}
|
|
|
|
|
2022-08-21 22:32:53 +00:00
|
|
|
// nolint:bodyclose
|
2022-08-01 04:05:35 +00:00
|
|
|
func TestUserOIDC(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
for _, tc := range []struct {
|
2022-11-25 10:10:09 +00:00
|
|
|
Name string
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims jwt.MapClaims
|
|
|
|
UserInfoClaims jwt.MapClaims
|
2022-11-25 10:10:09 +00:00
|
|
|
AllowSignups bool
|
2022-12-05 18:20:53 +00:00
|
|
|
EmailDomain []string
|
2022-11-25 10:10:09 +00:00
|
|
|
Username string
|
|
|
|
AvatarURL string
|
|
|
|
StatusCode int
|
|
|
|
IgnoreEmailVerified bool
|
2022-08-01 04:05:35 +00:00
|
|
|
}{{
|
2022-09-08 14:06:00 +00:00
|
|
|
Name: "EmailOnly",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-08-01 04:05:35 +00:00
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
},
|
|
|
|
AllowSignups: true,
|
2022-09-08 14:06:00 +00:00
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
|
Username: "kyle",
|
|
|
|
}, {
|
|
|
|
Name: "EmailNotVerified",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-09-08 14:06:00 +00:00
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
"email_verified": false,
|
|
|
|
},
|
|
|
|
AllowSignups: true,
|
2022-08-01 04:05:35 +00:00
|
|
|
StatusCode: http.StatusForbidden,
|
2022-11-25 10:10:09 +00:00
|
|
|
}, {
|
|
|
|
Name: "EmailNotAString",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-11-25 10:10:09 +00:00
|
|
|
"email": 3.14159,
|
|
|
|
"email_verified": false,
|
|
|
|
},
|
|
|
|
AllowSignups: true,
|
|
|
|
StatusCode: http.StatusBadRequest,
|
|
|
|
}, {
|
|
|
|
Name: "EmailNotVerifiedIgnored",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-11-25 10:10:09 +00:00
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
"email_verified": false,
|
|
|
|
},
|
|
|
|
AllowSignups: true,
|
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
|
Username: "kyle",
|
|
|
|
IgnoreEmailVerified: true,
|
2022-08-01 04:05:35 +00:00
|
|
|
}, {
|
|
|
|
Name: "NotInRequiredEmailDomain",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-08-01 04:05:35 +00:00
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
"email_verified": true,
|
|
|
|
},
|
|
|
|
AllowSignups: true,
|
2022-12-05 18:20:53 +00:00
|
|
|
EmailDomain: []string{
|
|
|
|
"coder.com",
|
|
|
|
},
|
|
|
|
StatusCode: http.StatusForbidden,
|
2022-10-13 15:51:54 +00:00
|
|
|
}, {
|
|
|
|
Name: "EmailDomainCaseInsensitive",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-10-13 15:51:54 +00:00
|
|
|
"email": "kyle@KWC.io",
|
|
|
|
"email_verified": true,
|
|
|
|
},
|
|
|
|
AllowSignups: true,
|
2022-12-05 18:20:53 +00:00
|
|
|
EmailDomain: []string{
|
|
|
|
"kwc.io",
|
|
|
|
},
|
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
2022-08-01 04:05:35 +00:00
|
|
|
}, {
|
2023-01-16 22:06:39 +00:00
|
|
|
Name: "EmptyClaims",
|
|
|
|
IDTokenClaims: jwt.MapClaims{},
|
|
|
|
AllowSignups: true,
|
|
|
|
StatusCode: http.StatusBadRequest,
|
2022-08-01 04:05:35 +00:00
|
|
|
}, {
|
|
|
|
Name: "NoSignups",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-08-01 04:05:35 +00:00
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
"email_verified": true,
|
|
|
|
},
|
|
|
|
StatusCode: http.StatusForbidden,
|
|
|
|
}, {
|
|
|
|
Name: "UsernameFromEmail",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-08-01 04:05:35 +00:00
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
"email_verified": true,
|
|
|
|
},
|
|
|
|
Username: "kyle",
|
|
|
|
AllowSignups: true,
|
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
|
}, {
|
|
|
|
Name: "UsernameFromClaims",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-08-01 04:05:35 +00:00
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
"email_verified": true,
|
|
|
|
"preferred_username": "hotdog",
|
|
|
|
},
|
|
|
|
Username: "hotdog",
|
|
|
|
AllowSignups: true,
|
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
|
}, {
|
|
|
|
// Services like Okta return the email as the username:
|
|
|
|
// https://developer.okta.com/docs/reference/api/oidc/#base-claims-always-present
|
|
|
|
Name: "UsernameAsEmail",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-08-01 04:05:35 +00:00
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
"email_verified": true,
|
|
|
|
"preferred_username": "kyle@kwc.io",
|
|
|
|
},
|
|
|
|
Username: "kyle",
|
|
|
|
AllowSignups: true,
|
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
2022-10-17 19:14:49 +00:00
|
|
|
}, {
|
|
|
|
// See: https://github.com/coder/coder/issues/4472
|
|
|
|
Name: "UsernameIsEmail",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-10-17 19:14:49 +00:00
|
|
|
"preferred_username": "kyle@kwc.io",
|
|
|
|
},
|
|
|
|
Username: "kyle",
|
|
|
|
AllowSignups: true,
|
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
2022-09-04 16:44:27 +00:00
|
|
|
}, {
|
|
|
|
Name: "WithPicture",
|
2023-01-16 22:06:39 +00:00
|
|
|
IDTokenClaims: jwt.MapClaims{
|
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
"email_verified": true,
|
|
|
|
"preferred_username": "kyle",
|
|
|
|
"picture": "/example.png",
|
|
|
|
},
|
|
|
|
Username: "kyle",
|
|
|
|
AllowSignups: true,
|
|
|
|
AvatarURL: "/example.png",
|
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
|
|
|
}, {
|
|
|
|
Name: "WithUserInfoClaims",
|
|
|
|
IDTokenClaims: jwt.MapClaims{
|
2022-09-04 16:44:27 +00:00
|
|
|
"email": "kyle@kwc.io",
|
|
|
|
"email_verified": true,
|
|
|
|
},
|
2023-01-16 22:06:39 +00:00
|
|
|
UserInfoClaims: jwt.MapClaims{
|
|
|
|
"preferred_username": "potato",
|
|
|
|
"picture": "/example.png",
|
|
|
|
},
|
|
|
|
Username: "potato",
|
2022-09-04 16:44:27 +00:00
|
|
|
AllowSignups: true,
|
|
|
|
AvatarURL: "/example.png",
|
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
2023-02-02 19:53:48 +00:00
|
|
|
}, {
|
|
|
|
Name: "GroupsDoesNothing",
|
|
|
|
IDTokenClaims: jwt.MapClaims{
|
|
|
|
"email": "coolin@coder.com",
|
|
|
|
"groups": []string{"pingpong"},
|
|
|
|
},
|
|
|
|
AllowSignups: true,
|
|
|
|
StatusCode: http.StatusTemporaryRedirect,
|
2022-08-01 04:05:35 +00:00
|
|
|
}} {
|
|
|
|
tc := tc
|
|
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2023-02-06 20:12:50 +00:00
|
|
|
auditor := audit.NewMock()
|
2022-10-18 03:07:11 +00:00
|
|
|
conf := coderdtest.NewOIDCConfig(t, "")
|
|
|
|
|
2023-01-16 22:06:39 +00:00
|
|
|
config := conf.OIDCConfig(t, tc.UserInfoClaims)
|
2022-08-01 04:05:35 +00:00
|
|
|
config.AllowSignups = tc.AllowSignups
|
|
|
|
config.EmailDomain = tc.EmailDomain
|
2022-11-25 10:10:09 +00:00
|
|
|
config.IgnoreEmailVerified = tc.IgnoreEmailVerified
|
2022-10-18 03:07:11 +00:00
|
|
|
|
2022-08-01 04:05:35 +00:00
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2023-02-06 20:12:50 +00:00
|
|
|
Auditor: auditor,
|
2022-08-01 04:05:35 +00:00
|
|
|
OIDCConfig: config,
|
|
|
|
})
|
2023-03-30 17:13:03 +00:00
|
|
|
numLogs := len(auditor.AuditLogs())
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2023-01-16 22:06:39 +00:00
|
|
|
resp := oidcCallback(t, client, conf.EncodeClaims(t, tc.IDTokenClaims))
|
2023-02-06 20:12:50 +00:00
|
|
|
numLogs++ // add an audit log for login
|
2022-08-01 04:05:35 +00:00
|
|
|
assert.Equal(t, tc.StatusCode, resp.StatusCode)
|
|
|
|
|
2023-03-23 22:42:20 +00:00
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
2022-08-09 17:17:00 +00:00
|
|
|
|
2022-08-01 04:05:35 +00:00
|
|
|
if tc.Username != "" {
|
2022-11-09 13:31:24 +00:00
|
|
|
client.SetSessionToken(authCookieValue(resp.Cookies()))
|
2022-08-09 17:17:00 +00:00
|
|
|
user, err := client.User(ctx, "me")
|
2022-08-01 04:05:35 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tc.Username, user.Username)
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2023-03-30 17:13:03 +00:00
|
|
|
require.Len(t, auditor.AuditLogs(), numLogs)
|
|
|
|
require.NotEqual(t, auditor.AuditLogs()[numLogs-1].UserID, uuid.Nil)
|
|
|
|
require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)
|
2022-08-01 04:05:35 +00:00
|
|
|
}
|
2022-09-04 16:44:27 +00:00
|
|
|
|
|
|
|
if tc.AvatarURL != "" {
|
2022-11-09 13:31:24 +00:00
|
|
|
client.SetSessionToken(authCookieValue(resp.Cookies()))
|
2022-09-04 16:44:27 +00:00
|
|
|
user, err := client.User(ctx, "me")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tc.AvatarURL, user.AvatarURL)
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2023-03-30 17:13:03 +00:00
|
|
|
require.Len(t, auditor.AuditLogs(), numLogs)
|
|
|
|
require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)
|
2022-09-04 16:44:27 +00:00
|
|
|
}
|
2022-08-01 04:05:35 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-10-18 03:07:11 +00:00
|
|
|
t.Run("AlternateUsername", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
2023-02-06 20:12:50 +00:00
|
|
|
auditor := audit.NewMock()
|
2022-10-18 03:07:11 +00:00
|
|
|
conf := coderdtest.NewOIDCConfig(t, "")
|
|
|
|
|
2023-01-16 22:06:39 +00:00
|
|
|
config := conf.OIDCConfig(t, nil)
|
2022-10-18 03:07:11 +00:00
|
|
|
config.AllowSignups = true
|
|
|
|
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
2023-02-06 20:12:50 +00:00
|
|
|
Auditor: auditor,
|
2022-10-18 03:07:11 +00:00
|
|
|
OIDCConfig: config,
|
|
|
|
})
|
2023-03-30 17:13:03 +00:00
|
|
|
numLogs := len(auditor.AuditLogs())
|
2022-10-18 03:07:11 +00:00
|
|
|
|
|
|
|
code := conf.EncodeClaims(t, jwt.MapClaims{
|
|
|
|
"email": "jon@coder.com",
|
|
|
|
})
|
|
|
|
resp := oidcCallback(t, client, code)
|
2023-02-06 20:12:50 +00:00
|
|
|
numLogs++ // add an audit log for login
|
|
|
|
|
2022-10-18 03:07:11 +00:00
|
|
|
assert.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
|
|
|
|
2023-03-23 22:42:20 +00:00
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
2022-10-18 03:07:11 +00:00
|
|
|
|
2022-11-09 13:31:24 +00:00
|
|
|
client.SetSessionToken(authCookieValue(resp.Cookies()))
|
2022-10-18 03:07:11 +00:00
|
|
|
user, err := client.User(ctx, "me")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, "jon", user.Username)
|
|
|
|
|
|
|
|
// Pass a different subject field so that we prompt creating a
|
|
|
|
// new user.
|
|
|
|
code = conf.EncodeClaims(t, jwt.MapClaims{
|
|
|
|
"email": "jon@example2.com",
|
|
|
|
"sub": "diff",
|
|
|
|
})
|
|
|
|
resp = oidcCallback(t, client, code)
|
2023-02-06 20:12:50 +00:00
|
|
|
numLogs++ // add an audit log for login
|
|
|
|
|
2022-10-18 03:07:11 +00:00
|
|
|
assert.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
|
|
|
|
2022-11-09 13:31:24 +00:00
|
|
|
client.SetSessionToken(authCookieValue(resp.Cookies()))
|
2022-10-18 03:07:11 +00:00
|
|
|
user, err = client.User(ctx, "me")
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, strings.HasPrefix(user.Username, "jon-"), "username %q should have prefix %q", user.Username, "jon-")
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2023-03-30 17:13:03 +00:00
|
|
|
require.Len(t, auditor.AuditLogs(), numLogs)
|
|
|
|
require.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[numLogs-1].Action)
|
2022-10-18 03:07:11 +00:00
|
|
|
})
|
|
|
|
|
2022-08-01 04:05:35 +00:00
|
|
|
t.Run("Disabled", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, nil)
|
2022-10-18 03:07:11 +00:00
|
|
|
resp := oidcCallback(t, client, "asdf")
|
2023-01-13 14:30:48 +00:00
|
|
|
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
2022-08-01 04:05:35 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("NoIDToken", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
OIDCConfig: &coderd.OIDCConfig{
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{},
|
2022-08-01 04:05:35 +00:00
|
|
|
},
|
|
|
|
})
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-10-18 03:07:11 +00:00
|
|
|
resp := oidcCallback(t, client, "asdf")
|
2022-08-01 04:05:35 +00:00
|
|
|
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("BadVerify", func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
verifier := oidc.NewVerifier("", &oidc.StaticKeySet{
|
|
|
|
PublicKeys: []crypto.PublicKey{},
|
|
|
|
}, &oidc.Config{})
|
2023-01-16 22:06:39 +00:00
|
|
|
provider := &oidc.Provider{}
|
2022-08-01 04:05:35 +00:00
|
|
|
|
|
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
|
|
OIDCConfig: &coderd.OIDCConfig{
|
2023-03-22 19:37:08 +00:00
|
|
|
OAuth2Config: &testutil.OAuth2Config{
|
|
|
|
Token: (&oauth2.Token{
|
2022-08-01 04:05:35 +00:00
|
|
|
AccessToken: "token",
|
|
|
|
}).WithExtra(map[string]interface{}{
|
|
|
|
"id_token": "invalid",
|
|
|
|
}),
|
|
|
|
},
|
2023-01-16 22:06:39 +00:00
|
|
|
Provider: provider,
|
2022-08-01 04:05:35 +00:00
|
|
|
Verifier: verifier,
|
|
|
|
},
|
|
|
|
})
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-10-18 03:07:11 +00:00
|
|
|
resp := oidcCallback(t, client, "asdf")
|
2023-02-06 20:12:50 +00:00
|
|
|
|
2022-08-01 04:05:35 +00:00
|
|
|
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-23 22:58:57 +00:00
|
|
|
func oauth2Callback(t *testing.T, client *codersdk.Client) *http.Response {
|
|
|
|
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
|
|
|
return http.ErrUseLastResponse
|
|
|
|
}
|
2022-10-18 03:07:11 +00:00
|
|
|
|
2022-04-23 22:58:57 +00:00
|
|
|
state := "somestate"
|
|
|
|
oauthURL, err := client.URL.Parse("/api/v2/users/oauth2/github/callback?code=asd&state=" + state)
|
|
|
|
require.NoError(t, err)
|
2022-08-21 22:32:53 +00:00
|
|
|
req, err := http.NewRequestWithContext(context.Background(), "GET", oauthURL.String(), nil)
|
2022-04-23 22:58:57 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
req.AddCookie(&http.Cookie{
|
2023-01-29 21:47:24 +00:00
|
|
|
Name: codersdk.OAuth2StateCookie,
|
2022-04-23 22:58:57 +00:00
|
|
|
Value: state,
|
|
|
|
})
|
|
|
|
res, err := client.HTTPClient.Do(req)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
_ = res.Body.Close()
|
|
|
|
})
|
|
|
|
return res
|
|
|
|
}
|
2022-08-01 04:05:35 +00:00
|
|
|
|
2022-10-18 03:07:11 +00:00
|
|
|
func oidcCallback(t *testing.T, client *codersdk.Client, code string) *http.Response {
|
2022-08-01 04:05:35 +00:00
|
|
|
t.Helper()
|
|
|
|
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
|
|
|
return http.ErrUseLastResponse
|
|
|
|
}
|
2022-10-18 03:07:11 +00:00
|
|
|
oauthURL, err := client.URL.Parse(fmt.Sprintf("/api/v2/users/oidc/callback?code=%s&state=somestate", code))
|
2022-08-01 04:05:35 +00:00
|
|
|
require.NoError(t, err)
|
2022-08-21 22:32:53 +00:00
|
|
|
req, err := http.NewRequestWithContext(context.Background(), "GET", oauthURL.String(), nil)
|
2022-08-01 04:05:35 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
req.AddCookie(&http.Cookie{
|
2023-01-29 21:47:24 +00:00
|
|
|
Name: codersdk.OAuth2StateCookie,
|
2022-10-18 03:07:11 +00:00
|
|
|
Value: "somestate",
|
2022-08-01 04:05:35 +00:00
|
|
|
})
|
|
|
|
res, err := client.HTTPClient.Do(req)
|
|
|
|
require.NoError(t, err)
|
|
|
|
defer res.Body.Close()
|
|
|
|
data, err := io.ReadAll(res.Body)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Log(string(data))
|
|
|
|
return res
|
|
|
|
}
|
2022-08-17 23:00:53 +00:00
|
|
|
|
|
|
|
func i64ptr(i int64) *int64 {
|
|
|
|
return &i
|
|
|
|
}
|
2022-09-13 19:26:46 +00:00
|
|
|
|
|
|
|
func authCookieValue(cookies []*http.Cookie) string {
|
|
|
|
for _, cookie := range cookies {
|
2023-01-29 21:47:24 +00:00
|
|
|
if cookie.Name == codersdk.SessionTokenCookie {
|
2022-09-13 19:26:46 +00:00
|
|
|
return cookie.Value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|