mirror of
https://github.com/coder/coder.git
synced 2025-03-14 10:09:57 +00:00
chore: add e2e tests for organization auditors
This commit is contained in:
@ -1076,7 +1076,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
|
||||
}).SetInitAuditRequest(func(params *audit.RequestParams) (*audit.Request[database.User], func()) {
|
||||
return audit.InitRequest[database.User](rw, params)
|
||||
})
|
||||
cookies, user, key, err := api.oauthLogin(r, params)
|
||||
cookies, user, key, err := api.oauthLogin(rw, r, params)
|
||||
defer params.CommitAuditLogs()
|
||||
if err != nil {
|
||||
if httpErr := idpsync.IsHTTPError(err); httpErr != nil {
|
||||
@ -1448,7 +1448,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
|
||||
}).SetInitAuditRequest(func(params *audit.RequestParams) (*audit.Request[database.User], func()) {
|
||||
return audit.InitRequest[database.User](rw, params)
|
||||
})
|
||||
cookies, user, key, err := api.oauthLogin(r, params)
|
||||
cookies, user, key, err := api.oauthLogin(rw, r, params)
|
||||
defer params.CommitAuditLogs()
|
||||
if err != nil {
|
||||
if hErr := idpsync.IsHTTPError(err); hErr != nil {
|
||||
@ -1621,7 +1621,7 @@ func (p *oauthLoginParams) CommitAuditLogs() {
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.Cookie, database.User, database.APIKey, error) {
|
||||
func (api *API) oauthLogin(rw http.ResponseWriter, r *http.Request, params *oauthLoginParams) ([]*http.Cookie, database.User, database.APIKey, error) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
user database.User
|
||||
@ -1756,7 +1756,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
|
||||
LoginType: params.LoginType,
|
||||
accountCreatorName: "oauth",
|
||||
RBACRoles: rbacRoles,
|
||||
})
|
||||
}, rw, r)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create user: %w", err)
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
LoginType: database.LoginTypePassword,
|
||||
RBACRoles: []string{rbac.RoleOwner().String()},
|
||||
accountCreatorName: "coder",
|
||||
})
|
||||
}, rw, r)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error creating user.",
|
||||
@ -485,7 +485,7 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
CreateUserRequestWithOrgs: req,
|
||||
LoginType: loginType,
|
||||
accountCreatorName: accountCreator.Name,
|
||||
})
|
||||
}, rw, r)
|
||||
|
||||
if dbauthz.IsNotAuthorizedError(err) {
|
||||
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
|
||||
@ -1364,7 +1364,7 @@ type CreateUserRequest struct {
|
||||
RBACRoles []string
|
||||
}
|
||||
|
||||
func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest) (database.User, error) {
|
||||
func (api *API) CreateUser(ctx context.Context, store database.Store, req CreateUserRequest, rw http.ResponseWriter, r *http.Request) (database.User, error) {
|
||||
// Ensure the username is valid. It's the caller's responsibility to ensure
|
||||
// the username is valid and unique.
|
||||
if usernameValid := codersdk.NameValid(req.Username); usernameValid != nil {
|
||||
@ -1380,8 +1380,6 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
|
||||
|
||||
var user database.User
|
||||
err := store.InTx(func(tx database.Store) error {
|
||||
orgRoles := make([]string, 0)
|
||||
|
||||
status := ""
|
||||
if req.UserStatus != nil {
|
||||
status = string(*req.UserStatus)
|
||||
@ -1429,13 +1427,21 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
|
||||
}
|
||||
|
||||
for _, orgID := range req.OrganizationIDs {
|
||||
aReq, commitAudit := audit.InitRequest[database.AuditableOrganizationMember](rw, &audit.RequestParams{
|
||||
OrganizationID: orgID,
|
||||
Audit: *api.Auditor.Load(),
|
||||
Log: api.Logger,
|
||||
Request: r,
|
||||
Action: database.AuditActionCreate,
|
||||
})
|
||||
aReq.Old = database.AuditableOrganizationMember{}
|
||||
defer commitAudit()
|
||||
_, err = tx.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{
|
||||
OrganizationID: orgID,
|
||||
UserID: user.ID,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
// By default give them membership to the organization.
|
||||
Roles: orgRoles,
|
||||
Roles: []string{},
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create organization member for %q: %w", orgID.String(), err)
|
||||
|
@ -319,7 +319,7 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
|
||||
LoginType: database.LoginTypeOIDC,
|
||||
// Do not send notifications to user admins as SCIM endpoint might be called sequentially to all users.
|
||||
SkipNotifications: true,
|
||||
})
|
||||
}, rw, r)
|
||||
if err != nil {
|
||||
_ = handlerutil.WriteError(rw, scim.NewHTTPError(http.StatusInternalServerError, "internalError", xerrors.Errorf("failed to create user: %w", err)))
|
||||
return
|
||||
|
@ -38,15 +38,25 @@ export const createUser = async (...orgIds: string[]) => {
|
||||
return user;
|
||||
};
|
||||
|
||||
export const createOrganizationMember = async (
|
||||
orgRoles: Record<string, string[]>,
|
||||
): Promise<LoginOptions> => {
|
||||
type CreateOrganizationMemberOptions = {
|
||||
username?: string;
|
||||
email?: string;
|
||||
password?: string;
|
||||
orgRoles: Record<string, string[]>;
|
||||
};
|
||||
|
||||
export const createOrganizationMember = async ({
|
||||
username = randomName(),
|
||||
email = `${username}@coder.com`,
|
||||
password = defaultPassword,
|
||||
orgRoles,
|
||||
}: CreateOrganizationMemberOptions): Promise<LoginOptions> => {
|
||||
const name = randomName();
|
||||
const user = await API.createUser({
|
||||
email: `${name}@coder.com`,
|
||||
username: name,
|
||||
name: name,
|
||||
password: defaultPassword,
|
||||
email,
|
||||
username,
|
||||
name: username,
|
||||
password,
|
||||
login_type: "password",
|
||||
organization_ids: Object.keys(orgRoles),
|
||||
user_status: null,
|
||||
@ -59,7 +69,7 @@ export const createOrganizationMember = async (
|
||||
return {
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
password: defaultPassword,
|
||||
password,
|
||||
};
|
||||
};
|
||||
|
||||
@ -74,8 +84,7 @@ export const createGroup = async (orgId: string) => {
|
||||
return group;
|
||||
};
|
||||
|
||||
export const createOrganization = async () => {
|
||||
const name = randomName();
|
||||
export const createOrganization = async (name = randomName()) => {
|
||||
const org = await API.createOrganization({
|
||||
name,
|
||||
display_name: `Org ${name}`,
|
||||
|
@ -34,7 +34,9 @@ test("create group", async ({ page }) => {
|
||||
// Create a new organization
|
||||
const org = await createOrganization();
|
||||
const orgUserAdmin = await createOrganizationMember({
|
||||
[org.id]: ["organization-user-admin"],
|
||||
orgRoles: {
|
||||
[org.id]: ["organization-user-admin"],
|
||||
},
|
||||
});
|
||||
|
||||
await login(page, orgUserAdmin);
|
||||
@ -99,7 +101,9 @@ test("change quota settings", async ({ page }) => {
|
||||
const org = await createOrganization();
|
||||
const group = await createGroup(org.id);
|
||||
const orgUserAdmin = await createOrganizationMember({
|
||||
[org.id]: ["organization-user-admin"],
|
||||
orgRoles: {
|
||||
[org.id]: ["organization-user-admin"],
|
||||
},
|
||||
});
|
||||
|
||||
// Go to settings
|
||||
|
142
site/e2e/tests/organizations/auditLogs.spec.ts
Normal file
142
site/e2e/tests/organizations/auditLogs.spec.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import { type Page, expect, test } from "@playwright/test";
|
||||
import { defaultPassword } from "../../constants";
|
||||
import {
|
||||
createTemplate,
|
||||
createWorkspace,
|
||||
currentUser,
|
||||
login,
|
||||
requiresLicense,
|
||||
randomName,
|
||||
} from "../../helpers";
|
||||
import { beforeCoderTest } from "../../hooks";
|
||||
import {
|
||||
createOrganization,
|
||||
createOrganizationMember,
|
||||
createUser,
|
||||
setupApiCalls,
|
||||
} from "../../api";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
|
||||
const orgName = randomName();
|
||||
|
||||
const orgAuditor = {
|
||||
username: `org-auditor-${orgName}`,
|
||||
password: defaultPassword,
|
||||
email: `org-auditor-${orgName}@coder.com`,
|
||||
};
|
||||
|
||||
test.beforeAll(async ({ page }) => {
|
||||
beforeCoderTest(page);
|
||||
await login(page);
|
||||
await setupApiCalls(page);
|
||||
|
||||
const org = await createOrganization(orgName);
|
||||
await createOrganizationMember({
|
||||
...orgAuditor,
|
||||
orgRoles: {
|
||||
[org.id]: ["organization-auditor"],
|
||||
},
|
||||
});
|
||||
|
||||
await page.context().clearCookies();
|
||||
});
|
||||
|
||||
async function resetSearch(page: Page, username: string) {
|
||||
const clearButton = page.getByLabel("Clear search");
|
||||
if (await clearButton.isVisible()) {
|
||||
await clearButton.click();
|
||||
}
|
||||
|
||||
// Filter by the auditor test user to prevent race conditions
|
||||
await expect(page.getByText("All users")).toBeVisible();
|
||||
await page.getByPlaceholder("Search...").fill(`username:${username}`);
|
||||
await expect(page.getByText("All users")).not.toBeVisible();
|
||||
}
|
||||
|
||||
test("organization auditors cannot see logins", async ({ page }) => {
|
||||
requiresLicense();
|
||||
|
||||
// Go to the audit history
|
||||
await login(page, orgAuditor);
|
||||
await page.goto("/audit");
|
||||
const username = orgAuditor.username;
|
||||
|
||||
const loginMessage = `${username} logged in`;
|
||||
// Make sure those things we did all actually show up
|
||||
await resetSearch(page, username);
|
||||
await expect(page.getByText(loginMessage).first()).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("creating users as organization members is logged", async ({ page }) => {
|
||||
requiresLicense();
|
||||
|
||||
// Do some stuff that should show up in the audit logs
|
||||
await login(page, orgAuditor);
|
||||
const username = orgAuditor.username;
|
||||
|
||||
// Go to the audit history
|
||||
await page.goto("/audit", { waitUntil: "domcontentloaded" });
|
||||
|
||||
// Make sure those things we did all actually show up
|
||||
await resetSearch(page, username);
|
||||
// await expect(
|
||||
// page.getByText(`${username} created template ${templateName}`),
|
||||
// ).toBeVisible();
|
||||
// await expect(
|
||||
// page.getByText(`${username} created workspace ${workspaceName}`),
|
||||
// ).toBeVisible();
|
||||
// await expect(
|
||||
// page.getByText(`${username} started workspace ${workspaceName}`),
|
||||
// ).toBeVisible();
|
||||
|
||||
// Make sure we can inspect the details of the log item
|
||||
// const createdWorkspace = page.locator(".MuiTableRow-root", {
|
||||
// hasText: `${username} created workspace ${workspaceName}`,
|
||||
// });
|
||||
// await createdWorkspace.getByLabel("open-dropdown").click();
|
||||
// await expect(
|
||||
// createdWorkspace.getByText(`automatic_updates: "never"`),
|
||||
// ).toBeVisible();
|
||||
// await expect(
|
||||
// createdWorkspace.getByText(`name: "${workspaceName}"`),
|
||||
// ).toBeVisible();
|
||||
});
|
||||
|
||||
// test("inspecting and filtering audit logs", async ({ page }) => {
|
||||
// requiresLicense();
|
||||
|
||||
// // Do some stuff that should show up in the audit logs
|
||||
// await login(page, users.templateAdmin);
|
||||
// const username = users.templateAdmin.username;
|
||||
// const templateName = await createTemplate(page);
|
||||
// const workspaceName = await createWorkspace(page, templateName);
|
||||
|
||||
// // Go to the audit history
|
||||
// await login(page, users.auditor);
|
||||
// await page.goto("/audit");
|
||||
// const loginMessage = `${username} logged in`;
|
||||
// const startedWorkspaceMessage = `${username} started workspace ${workspaceName}`;
|
||||
|
||||
// // Filter by resource type
|
||||
// await resetSearch(page, username);
|
||||
// await page.getByText("All resource types").click();
|
||||
// const workspaceBuildsOption = page.getByText("Workspace Build");
|
||||
// await workspaceBuildsOption.scrollIntoViewIfNeeded({ timeout: 5000 });
|
||||
// await workspaceBuildsOption.click();
|
||||
// // Our workspace build should be visible
|
||||
// await expect(page.getByText(startedWorkspaceMessage)).toBeVisible();
|
||||
// // Logins should no longer be visible
|
||||
// await expect(page.getByText(loginMessage)).not.toBeVisible();
|
||||
// await page.getByLabel("Clear search").click();
|
||||
// await expect(page.getByText("All resource types")).toBeVisible();
|
||||
|
||||
// // Filter by action type
|
||||
// await resetSearch(page, username);
|
||||
// await page.getByText("All actions").click();
|
||||
// await page.getByText("Login", { exact: true }).click();
|
||||
// // Logins should be visible
|
||||
// await expect(page.getByText(loginMessage).first()).toBeVisible();
|
||||
// // Our workspace build should no longer be visible
|
||||
// await expect(page.getByText(startedWorkspaceMessage)).not.toBeVisible();
|
||||
// });
|
@ -106,7 +106,9 @@ test.describe("org-scoped roles admin settings access", () => {
|
||||
test("org template admin can see admin settings", async ({ page }) => {
|
||||
const org = await createOrganization();
|
||||
const orgTemplateAdmin = await createOrganizationMember({
|
||||
[org.id]: ["organization-template-admin"],
|
||||
orgRoles: {
|
||||
[org.id]: ["organization-template-admin"],
|
||||
},
|
||||
});
|
||||
|
||||
await login(page, orgTemplateAdmin);
|
||||
@ -118,7 +120,9 @@ test.describe("org-scoped roles admin settings access", () => {
|
||||
test("org user admin can see admin settings", async ({ page }) => {
|
||||
const org = await createOrganization();
|
||||
const orgUserAdmin = await createOrganizationMember({
|
||||
[org.id]: ["organization-user-admin"],
|
||||
orgRoles: {
|
||||
[org.id]: ["organization-user-admin"],
|
||||
},
|
||||
});
|
||||
|
||||
await login(page, orgUserAdmin);
|
||||
@ -130,7 +134,9 @@ test.describe("org-scoped roles admin settings access", () => {
|
||||
test("org auditor can see admin settings", async ({ page }) => {
|
||||
const org = await createOrganization();
|
||||
const orgAuditor = await createOrganizationMember({
|
||||
[org.id]: ["organization-auditor"],
|
||||
orgRoles: {
|
||||
[org.id]: ["organization-auditor"],
|
||||
},
|
||||
});
|
||||
|
||||
await login(page, orgAuditor);
|
||||
@ -142,7 +148,9 @@ test.describe("org-scoped roles admin settings access", () => {
|
||||
test("org admin can see admin settings", async ({ page }) => {
|
||||
const org = await createOrganization();
|
||||
const orgAdmin = await createOrganizationMember({
|
||||
[org.id]: ["organization-admin"],
|
||||
orgRoles: {
|
||||
[org.id]: ["organization-admin"],
|
||||
},
|
||||
});
|
||||
|
||||
await login(page, orgAdmin);
|
||||
|
Reference in New Issue
Block a user