Skip to content

Commit

Permalink
stash
Browse files Browse the repository at this point in the history
  • Loading branch information
kimlisa committed Feb 7, 2024
1 parent eafa174 commit dde0b2b
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 25 deletions.
30 changes: 27 additions & 3 deletions lib/web/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package web
import (
"context"
"net/http"
"net/url"
"time"

"github.com/gravitational/trace"
Expand All @@ -30,6 +31,7 @@ import (
"github.com/gravitational/teleport/api/mfa"
"github.com/gravitational/teleport/api/types"
wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/httplib"
"github.com/gravitational/teleport/lib/web/ui"
)
Expand Down Expand Up @@ -58,7 +60,7 @@ func (h *Handler) getUsersHandle(w http.ResponseWriter, r *http.Request, params
return nil, trace.Wrap(err)
}

return getUsers(r.Context(), clt)
return getUsers(r.Context(), clt, r.URL.Query())
}

func (h *Handler) getUserHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) {
Expand Down Expand Up @@ -179,12 +181,32 @@ func updateUser(r *http.Request, m userAPIGetter, createdBy string) (*ui.User, e
return ui.NewUser(updated)
}

func getUsers(ctx context.Context, m userAPIGetter) ([]ui.UserListEntry, error) {
users, err := m.GetUsers(ctx, false)
func getUsers(ctx context.Context, m userAPIGetter, urlVals url.Values) ([]ui.UserListEntry, error) {
limit, err := queryLimitAsInt32(urlVals, "limit", defaults.MaxIterationLimit)
if err != nil {
return nil, trace.Wrap(err)
}

var users []types.User
if limit > 0 {
// Use the new list user with pagination support api
users, _, err = m.ListUsers(ctx, int(limit), "" /* nextToken */, false /* withSecrets*/)
switch {
case trace.IsNotImplemented(err):
users, err = m.GetUsers(ctx, false /* withSecrets*/)
if err != nil {
return nil, trace.Wrap(err)
}
case err != nil:
return nil, trace.Wrap(err)
}
} else {
users, err = m.GetUsers(ctx, false /* withSecrets*/)
if err != nil {
return nil, trace.Wrap(err)
}
}

var uiUsers []ui.UserListEntry
for _, u := range users {
// Do not display system users in the WebUI
Expand Down Expand Up @@ -285,6 +307,8 @@ type userAPIGetter interface {
GetUsers(ctx context.Context, withSecrets bool) ([]types.User, error)
// DeleteUser deletes a user by name.
DeleteUser(ctx context.Context, user string) error
// ListUsers returns a page of users.
ListUsers(ctx context.Context, pageSize int, nextToken string, withSecrets bool) ([]types.User, string, error)
}

type userTraits struct {
Expand Down
40 changes: 38 additions & 2 deletions lib/web/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"context"
"encoding/json"
"net/http"
"net/url"
"testing"

"github.com/gravitational/trace"
Expand Down Expand Up @@ -106,7 +107,7 @@ func TestCRUDs(t *testing.T) {
require.Contains(t, user.Roles, "newrole")

// test list
users, err := getUsers(context.Background(), m)
users, err := getUsers(context.Background(), m, url.Values{})
require.NoError(t, err)
require.Len(t, users, 1)
require.Equal(t, "testname", users[0].Name)
Expand All @@ -120,6 +121,32 @@ func TestCRUDs(t *testing.T) {
require.NoError(t, err)
}

func TestGetUserWithLimit(t *testing.T) {
m := &mockedUserAPIGetter{}
m.mockListUsers = func(ctx context.Context, pageSize int, nextToken string, withSecrets bool) ([]types.User, string, error) {
user1, err := types.NewUser("user-1")
if err != nil {
return nil, "", err
}
user2, err := types.NewUser("user-2")
if err != nil {
return nil, "", err
}
return []types.User{user1, user2}, "", nil
}

m.mockGetUsers = func(ctx context.Context, withSecrets bool) ([]types.User, error) {
return nil, trace.NotImplemented("called the fallback get user when not supposed to")
}

// test list
users, err := getUsers(context.Background(), m, url.Values{"limit": {"2"}})
require.NoError(t, err)
require.Len(t, users, 2)
require.Equal(t, "user-1", users[0].Name)
require.Equal(t, "user-2", users[1].Name)
}

func TestUpdateUser_setTraits(t *testing.T) {
defaultRoles := []string{"role1"}
defaultLogins := []string{"login1"}
Expand Down Expand Up @@ -306,7 +333,7 @@ func TestCRUDErrors(t *testing.T) {
require.True(t, trace.IsAlreadyExists(err))
require.Nil(t, user)

users, err := getUsers(context.Background(), m)
users, err := getUsers(context.Background(), m, url.Values{})
require.True(t, trace.IsAccessDenied(err))
require.Nil(t, users)

Expand Down Expand Up @@ -345,6 +372,7 @@ type mockedUserAPIGetter struct {
mockUpdateUser func(ctx context.Context, user types.User) (types.User, error)
mockGetUsers func(ctx context.Context, withSecrets bool) ([]types.User, error)
mockDeleteUser func(ctx context.Context, user string) error
mockListUsers func(ctx context.Context, pageSize int, nextToken string, withSecrets bool) ([]types.User, string, error)
}

func (m *mockedUserAPIGetter) GetUser(ctx context.Context, name string, withSecrets bool) (types.User, error) {
Expand Down Expand Up @@ -382,3 +410,11 @@ func (m *mockedUserAPIGetter) DeleteUser(ctx context.Context, name string) error

return trace.NotImplemented("mockDeleteUser not implemented")
}

func (m *mockedUserAPIGetter) ListUsers(ctx context.Context, pageSize int, nextToken string, withSecrets bool) ([]types.User, string, error) {
if m.mockListUsers != nil {
return m.mockListUsers(ctx, pageSize, nextToken, withSecrets)
}

return nil, "", trace.NotImplemented("mockListUsers not implemented")
}
18 changes: 6 additions & 12 deletions web/packages/teleport/src/Welcome/NewCredentials/Success.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,29 @@ import { OnboardCard } from 'design/Onboard/OnboardCard';

import { CaptureEvent, userEventService } from 'teleport/services/userEvent';
import shieldCheck from 'teleport/assets/shield-check.png';

import useTeleport from 'teleport/useTeleport';
import cfg from 'teleport/config';
import { storageService } from 'teleport/services/storageService';

import { RegisterSuccessProps } from './types';

export function RegisterSuccess({
redirect,
resetMode = false,
username = '',
version = '',
isEnterprise,
isDashboard,
}: RegisterSuccessProps) {
const actionTxt = resetMode ? 'reset' : 'registration';
const ctx = useTeleport();
version = ctx.storeUser.state.cluster.authVersion;

const handleRedirect = () => {
if (username) {
userEventService.capturePreUserEvent({
event: CaptureEvent.PreUserCompleteGoToDashboardClickEvent,
username: username,
});
// If not in reset mode, open a new tab to Teleport Registration page
// before redirecting to dashboard.
// next only do this for OSS
isEnterprise = false;
if (!isEnterprise && !resetMode) {
window.open(`https://goteleport.com/r/product-registration?${version}`, '_blank');
// The storage flag will be used later to determine if the user should
// also be directed to the teleport register page.
if (!cfg.isEnterprise && !resetMode) {
storageService.setNewUserMarker();
}
}
redirect();
Expand Down
2 changes: 0 additions & 2 deletions web/packages/teleport/src/Welcome/NewCredentials/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ export type RegisterSuccessProps = {
redirect(): void;
resetMode: boolean;
username?: string;
version?: string;
isDashboard: boolean;
isEnterprise?: boolean;
};

export type LoginFlow = Extract<PrimaryAuthType, 'passwordless' | 'local'>;
Expand Down
10 changes: 7 additions & 3 deletions web/packages/teleport/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ const cfg = {
kubernetesPath:
'/v1/webapi/sites/:clusterId/kubernetes?searchAsRoles=:searchAsRoles?&limit=:limit?&startKey=:startKey?&query=:query?&search=:search?&sort=:sort?',

usersPath: '/v1/webapi/users',
usersPath: '/v1/webapi/users?limit=:limit?',
userWithUsernamePath: '/v1/webapi/users/:username',
createPrivilegeTokenPath: '/v1/webapi/users/privilege/token',

Expand Down Expand Up @@ -614,8 +614,8 @@ const cfg = {
return generatePath(cfg.routes.kubernetes, { clusterId });
},

getUsersUrl() {
return cfg.api.usersPath;
getUsersUrl(params?: UrlUsersParams) {
return generatePath(cfg.api.usersPath, { ...params });
},

getUserWithUsernameUrl(username: string) {
Expand Down Expand Up @@ -1113,6 +1113,10 @@ export interface UrlDesktopParams {
clusterId: string;
}

export interface UrlUsersParams {
limit?: number;
}

export interface UrlResourcesParams {
query?: string;
search?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ export const storageService = {
return null;
},

setNewUserMarker() {
window.localStorage.setItem(KeysEnum.NEW_USER, 'true');
},

getNewUserMarker(): boolean {
const val = window.localStorage.getItem(KeysEnum.NEW_USER);
return Boolean(val);
},

removeNewUserMarker() {
window.localStorage.removeItem(KeysEnum.NEW_USER);
},

getUserPreferences(): UserPreferences {
const preferences = window.localStorage.getItem(KeysEnum.USER_PREFERENCES);
if (preferences) {
Expand Down
1 change: 1 addition & 0 deletions web/packages/teleport/src/services/storageService/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

export const KeysEnum = {
NEW_USER: `grv_teleport_new_user`,
TOKEN: 'grv_teleport_token',
TOKEN_RENEW: 'grv_teleport_token_renew',
LAST_ACTIVE: 'grv_teleport_last_active',
Expand Down
6 changes: 3 additions & 3 deletions web/packages/teleport/src/services/user/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*/

import api from 'teleport/services/api';
import cfg from 'teleport/config';
import cfg, { UrlUsersParams } from 'teleport/config';
import session from 'teleport/services/websession';

import { WebauthnAssertionResponse } from '../auth';
Expand Down Expand Up @@ -54,8 +54,8 @@ const service = {
return api.get(cfg.getUserWithUsernameUrl(username)).then(makeUser);
},

fetchUsers() {
return api.get(cfg.getUsersUrl()).then(makeUsers);
fetchUsers(params?: UrlUsersParams) {
return api.get(cfg.getUsersUrl(params)).then(makeUsers);
},

updateUser(user: User) {
Expand Down
22 changes: 22 additions & 0 deletions web/packages/teleport/src/teleportContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,28 @@ class TeleportContext implements types.Context {
const user = await userService.fetchUserContext();
this.storeUser.setState(user);

if (storageService.getNewUserMarker()) {
userService
.fetchUsers({ limit: 2 })
.then(users => {
// Only one means the current user is the only user existing
// in cluster.
if (users.length === 1) {
// Opens a new tab to the teleport register page.
window.open(
`https://goteleport.com/r/product-registration?${user.cluster.authVersion}`,
'_blank',
'noreferrer,noopener'
);
}
})
// Ignore any error.
.catch();

// Checking only once is required.
storageService.removeNewUserMarker();
}

if (
this.storeUser.hasPrereqAccessToAddAgents() &&
this.storeUser.hasAccessToQueryAgent() &&
Expand Down

0 comments on commit dde0b2b

Please sign in to comment.