mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add organization details to audit log response (#13961)
* Allow creating test audits with nil org Not all audit entries have organization IDs, so this will allow us to test those cases. * Add organization details to audit log queries * Add organization to audit log response This replaces the old ID. This is a breaking change but organizations were not being used before.
This commit is contained in:
@@ -32,8 +32,10 @@ func TestCurrentOrganization(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode([]codersdk.Organization{
|
||||
{
|
||||
ID: orgID,
|
||||
Name: "not-default",
|
||||
MinimalOrganization: codersdk.MinimalOrganization{
|
||||
ID: orgID,
|
||||
Name: "not-default",
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
IsDefault: false,
|
||||
|
||||
Generated
+25
@@ -8368,7 +8368,11 @@ const docTemplate = `{
|
||||
"is_deleted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"organization": {
|
||||
"$ref": "#/definitions/codersdk.MinimalOrganization"
|
||||
},
|
||||
"organization_id": {
|
||||
"description": "Deprecated: Use 'organization.id' instead.",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
@@ -10102,6 +10106,27 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.MinimalOrganization": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.MinimalUser": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
Generated
+23
@@ -7434,7 +7434,11 @@
|
||||
"is_deleted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"organization": {
|
||||
"$ref": "#/definitions/codersdk.MinimalOrganization"
|
||||
},
|
||||
"organization_id": {
|
||||
"description": "Deprecated: Use 'organization.id' instead.",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
@@ -9054,6 +9058,25 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.MinimalOrganization": {
|
||||
"type": "object",
|
||||
"required": ["id"],
|
||||
"properties": {
|
||||
"display_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.MinimalUser": {
|
||||
"type": "object",
|
||||
"required": ["id", "username"],
|
||||
|
||||
+16
-7
@@ -145,9 +145,6 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
|
||||
if len(params.AdditionalFields) == 0 {
|
||||
params.AdditionalFields = json.RawMessage("{}")
|
||||
}
|
||||
if params.OrganizationID == uuid.Nil {
|
||||
params.OrganizationID = uuid.New()
|
||||
}
|
||||
|
||||
_, err = api.Database.InsertAuditLog(ctx, database.InsertAuditLogParams{
|
||||
ID: uuid.New(),
|
||||
@@ -241,10 +238,11 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
|
||||
resourceLink = api.auditLogResourceLink(ctx, dblog, additionalFields)
|
||||
}
|
||||
|
||||
return codersdk.AuditLog{
|
||||
ID: dblog.ID,
|
||||
RequestID: dblog.RequestID,
|
||||
Time: dblog.Time,
|
||||
alog := codersdk.AuditLog{
|
||||
ID: dblog.ID,
|
||||
RequestID: dblog.RequestID,
|
||||
Time: dblog.Time,
|
||||
// OrganizationID is deprecated.
|
||||
OrganizationID: dblog.OrganizationID,
|
||||
IP: ip,
|
||||
UserAgent: dblog.UserAgent.String,
|
||||
@@ -261,6 +259,17 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
|
||||
ResourceLink: resourceLink,
|
||||
IsDeleted: isDeleted,
|
||||
}
|
||||
|
||||
if dblog.OrganizationID != uuid.Nil {
|
||||
alog.Organization = &codersdk.MinimalOrganization{
|
||||
ID: dblog.OrganizationID,
|
||||
Name: dblog.OrganizationName,
|
||||
DisplayName: dblog.OrganizationDisplayName,
|
||||
Icon: dblog.OrganizationIcon,
|
||||
}
|
||||
}
|
||||
|
||||
return alog
|
||||
}
|
||||
|
||||
func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
|
||||
|
||||
+88
-3
@@ -46,7 +46,7 @@ func TestAuditLogs(t *testing.T) {
|
||||
require.Len(t, alogs.AuditLogs, 1)
|
||||
})
|
||||
|
||||
t.Run("User", func(t *testing.T) {
|
||||
t.Run("IncludeUser", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -95,6 +95,92 @@ func TestAuditLogs(t *testing.T) {
|
||||
require.Equal(t, foundUser, *alogs.AuditLogs[0].User)
|
||||
})
|
||||
|
||||
t.Run("IncludeOrganization", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
client := coderdtest.New(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
o, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
|
||||
Name: "new-org",
|
||||
DisplayName: "New organization",
|
||||
Description: "A new organization to love and cherish until the test is over.",
|
||||
Icon: "/emojis/1f48f-1f3ff.png",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
|
||||
OrganizationID: o.ID,
|
||||
ResourceID: user.UserID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
alogs, err := client.AuditLogs(ctx, codersdk.AuditLogsRequest{
|
||||
Pagination: codersdk.Pagination{
|
||||
Limit: 1,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), alogs.Count)
|
||||
require.Len(t, alogs.AuditLogs, 1)
|
||||
|
||||
// Make sure the organization is fully populated.
|
||||
require.Equal(t, &codersdk.MinimalOrganization{
|
||||
ID: o.ID,
|
||||
Name: o.Name,
|
||||
DisplayName: o.DisplayName,
|
||||
Icon: o.Icon,
|
||||
}, alogs.AuditLogs[0].Organization)
|
||||
|
||||
// OrganizationID is deprecated, but make sure it is set.
|
||||
require.Equal(t, o.ID, alogs.AuditLogs[0].OrganizationID)
|
||||
|
||||
// Delete the org and try again, should be mostly empty.
|
||||
err = client.DeleteOrganization(ctx, o.ID.String())
|
||||
require.NoError(t, err)
|
||||
|
||||
alogs, err = client.AuditLogs(ctx, codersdk.AuditLogsRequest{
|
||||
Pagination: codersdk.Pagination{
|
||||
Limit: 1,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), alogs.Count)
|
||||
require.Len(t, alogs.AuditLogs, 1)
|
||||
|
||||
require.Equal(t, &codersdk.MinimalOrganization{
|
||||
ID: o.ID,
|
||||
}, alogs.AuditLogs[0].Organization)
|
||||
|
||||
// OrganizationID is deprecated, but make sure it is set.
|
||||
require.Equal(t, o.ID, alogs.AuditLogs[0].OrganizationID)
|
||||
|
||||
// Some audit entries do not have an organization at all, in which case the
|
||||
// response omits the organization.
|
||||
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
|
||||
ResourceType: codersdk.ResourceTypeAPIKey,
|
||||
ResourceID: user.UserID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
alogs, err = client.AuditLogs(ctx, codersdk.AuditLogsRequest{
|
||||
SearchQuery: "resource_type:api_key",
|
||||
Pagination: codersdk.Pagination{
|
||||
Limit: 1,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(1), alogs.Count)
|
||||
require.Len(t, alogs.AuditLogs, 1)
|
||||
|
||||
// The other will have no organization.
|
||||
require.Equal(t, (*codersdk.MinimalOrganization)(nil), alogs.AuditLogs[0].Organization)
|
||||
|
||||
// OrganizationID is deprecated, but make sure it is empty.
|
||||
require.Equal(t, uuid.Nil, alogs.AuditLogs[0].OrganizationID)
|
||||
})
|
||||
|
||||
t.Run("WorkspaceBuildAuditLink", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -159,8 +245,7 @@ func TestAuditLogs(t *testing.T) {
|
||||
|
||||
// Add an extra audit log in another organization
|
||||
err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{
|
||||
ResourceID: owner.UserID,
|
||||
OrganizationID: uuid.New(),
|
||||
ResourceID: owner.UserID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -928,6 +928,16 @@ func (q *FakeQuerier) getLatestWorkspaceAppByTemplateIDUserIDSlugNoLock(ctx cont
|
||||
return database.WorkspaceApp{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
// getOrganizationByIDNoLock is used by other functions in the database fake.
|
||||
func (q *FakeQuerier) getOrganizationByIDNoLock(id uuid.UUID) (database.Organization, error) {
|
||||
for _, organization := range q.organizations {
|
||||
if organization.ID == id {
|
||||
return organization, nil
|
||||
}
|
||||
}
|
||||
return database.Organization{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (*FakeQuerier) AcquireLock(_ context.Context, _ int64) error {
|
||||
return xerrors.New("AcquireLock must only be called within a transaction")
|
||||
}
|
||||
@@ -2146,34 +2156,39 @@ func (q *FakeQuerier) GetAuditLogsOffset(_ context.Context, arg database.GetAudi
|
||||
user, err := q.getUserByIDNoLock(alog.UserID)
|
||||
userValid := err == nil
|
||||
|
||||
org, _ := q.getOrganizationByIDNoLock(alog.OrganizationID)
|
||||
|
||||
logs = append(logs, database.GetAuditLogsOffsetRow{
|
||||
ID: alog.ID,
|
||||
RequestID: alog.RequestID,
|
||||
OrganizationID: alog.OrganizationID,
|
||||
Ip: alog.Ip,
|
||||
UserAgent: alog.UserAgent,
|
||||
ResourceType: alog.ResourceType,
|
||||
ResourceID: alog.ResourceID,
|
||||
ResourceTarget: alog.ResourceTarget,
|
||||
ResourceIcon: alog.ResourceIcon,
|
||||
Action: alog.Action,
|
||||
Diff: alog.Diff,
|
||||
StatusCode: alog.StatusCode,
|
||||
AdditionalFields: alog.AdditionalFields,
|
||||
UserID: alog.UserID,
|
||||
UserUsername: sql.NullString{String: user.Username, Valid: userValid},
|
||||
UserName: sql.NullString{String: user.Name, Valid: userValid},
|
||||
UserEmail: sql.NullString{String: user.Email, Valid: userValid},
|
||||
UserCreatedAt: sql.NullTime{Time: user.CreatedAt, Valid: userValid},
|
||||
UserUpdatedAt: sql.NullTime{Time: user.UpdatedAt, Valid: userValid},
|
||||
UserLastSeenAt: sql.NullTime{Time: user.LastSeenAt, Valid: userValid},
|
||||
UserLoginType: database.NullLoginType{LoginType: user.LoginType, Valid: userValid},
|
||||
UserDeleted: sql.NullBool{Bool: user.Deleted, Valid: userValid},
|
||||
UserThemePreference: sql.NullString{String: user.ThemePreference, Valid: userValid},
|
||||
UserQuietHoursSchedule: sql.NullString{String: user.QuietHoursSchedule, Valid: userValid},
|
||||
UserStatus: database.NullUserStatus{UserStatus: user.Status, Valid: userValid},
|
||||
UserRoles: user.RBACRoles,
|
||||
Count: 0,
|
||||
ID: alog.ID,
|
||||
RequestID: alog.RequestID,
|
||||
OrganizationID: alog.OrganizationID,
|
||||
OrganizationName: org.Name,
|
||||
OrganizationDisplayName: org.DisplayName,
|
||||
OrganizationIcon: org.Icon,
|
||||
Ip: alog.Ip,
|
||||
UserAgent: alog.UserAgent,
|
||||
ResourceType: alog.ResourceType,
|
||||
ResourceID: alog.ResourceID,
|
||||
ResourceTarget: alog.ResourceTarget,
|
||||
ResourceIcon: alog.ResourceIcon,
|
||||
Action: alog.Action,
|
||||
Diff: alog.Diff,
|
||||
StatusCode: alog.StatusCode,
|
||||
AdditionalFields: alog.AdditionalFields,
|
||||
UserID: alog.UserID,
|
||||
UserUsername: sql.NullString{String: user.Username, Valid: userValid},
|
||||
UserName: sql.NullString{String: user.Name, Valid: userValid},
|
||||
UserEmail: sql.NullString{String: user.Email, Valid: userValid},
|
||||
UserCreatedAt: sql.NullTime{Time: user.CreatedAt, Valid: userValid},
|
||||
UserUpdatedAt: sql.NullTime{Time: user.UpdatedAt, Valid: userValid},
|
||||
UserLastSeenAt: sql.NullTime{Time: user.LastSeenAt, Valid: userValid},
|
||||
UserLoginType: database.NullLoginType{LoginType: user.LoginType, Valid: userValid},
|
||||
UserDeleted: sql.NullBool{Bool: user.Deleted, Valid: userValid},
|
||||
UserThemePreference: sql.NullString{String: user.ThemePreference, Valid: userValid},
|
||||
UserQuietHoursSchedule: sql.NullString{String: user.QuietHoursSchedule, Valid: userValid},
|
||||
UserStatus: database.NullUserStatus{UserStatus: user.Status, Valid: userValid},
|
||||
UserRoles: user.RBACRoles,
|
||||
Count: 0,
|
||||
})
|
||||
|
||||
if len(logs) >= int(arg.LimitOpt) {
|
||||
@@ -2969,12 +2984,7 @@ func (q *FakeQuerier) GetOrganizationByID(_ context.Context, id uuid.UUID) (data
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, organization := range q.organizations {
|
||||
if organization.ID == id {
|
||||
return organization, nil
|
||||
}
|
||||
}
|
||||
return database.Organization{}, sql.ErrNoRows
|
||||
return q.getOrganizationByIDNoLock(id)
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetOrganizationByName(_ context.Context, name string) (database.Organization, error) {
|
||||
|
||||
@@ -459,6 +459,9 @@ SELECT
|
||||
users.deleted AS user_deleted,
|
||||
users.theme_preference AS user_theme_preference,
|
||||
users.quiet_hours_schedule AS user_quiet_hours_schedule,
|
||||
COALESCE(organizations.name, '') AS organization_name,
|
||||
COALESCE(organizations.display_name, '') AS organization_display_name,
|
||||
COALESCE(organizations.icon, '') AS organization_icon,
|
||||
COUNT(audit_logs.*) OVER () AS count
|
||||
FROM
|
||||
audit_logs
|
||||
@@ -487,6 +490,7 @@ FROM
|
||||
workspaces.id = workspace_builds.workspace_id AND
|
||||
workspace_builds.build_number = 1
|
||||
)
|
||||
LEFT JOIN organizations ON audit_logs.organization_id = organizations.id
|
||||
WHERE
|
||||
-- Filter resource_type
|
||||
CASE
|
||||
@@ -582,35 +586,38 @@ type GetAuditLogsOffsetParams struct {
|
||||
}
|
||||
|
||||
type GetAuditLogsOffsetRow struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Time time.Time `db:"time" json:"time"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
Ip pqtype.Inet `db:"ip" json:"ip"`
|
||||
UserAgent sql.NullString `db:"user_agent" json:"user_agent"`
|
||||
ResourceType ResourceType `db:"resource_type" json:"resource_type"`
|
||||
ResourceID uuid.UUID `db:"resource_id" json:"resource_id"`
|
||||
ResourceTarget string `db:"resource_target" json:"resource_target"`
|
||||
Action AuditAction `db:"action" json:"action"`
|
||||
Diff json.RawMessage `db:"diff" json:"diff"`
|
||||
StatusCode int32 `db:"status_code" json:"status_code"`
|
||||
AdditionalFields json.RawMessage `db:"additional_fields" json:"additional_fields"`
|
||||
RequestID uuid.UUID `db:"request_id" json:"request_id"`
|
||||
ResourceIcon string `db:"resource_icon" json:"resource_icon"`
|
||||
UserUsername sql.NullString `db:"user_username" json:"user_username"`
|
||||
UserName sql.NullString `db:"user_name" json:"user_name"`
|
||||
UserEmail sql.NullString `db:"user_email" json:"user_email"`
|
||||
UserCreatedAt sql.NullTime `db:"user_created_at" json:"user_created_at"`
|
||||
UserUpdatedAt sql.NullTime `db:"user_updated_at" json:"user_updated_at"`
|
||||
UserLastSeenAt sql.NullTime `db:"user_last_seen_at" json:"user_last_seen_at"`
|
||||
UserStatus NullUserStatus `db:"user_status" json:"user_status"`
|
||||
UserLoginType NullLoginType `db:"user_login_type" json:"user_login_type"`
|
||||
UserRoles pq.StringArray `db:"user_roles" json:"user_roles"`
|
||||
UserAvatarUrl sql.NullString `db:"user_avatar_url" json:"user_avatar_url"`
|
||||
UserDeleted sql.NullBool `db:"user_deleted" json:"user_deleted"`
|
||||
UserThemePreference sql.NullString `db:"user_theme_preference" json:"user_theme_preference"`
|
||||
UserQuietHoursSchedule sql.NullString `db:"user_quiet_hours_schedule" json:"user_quiet_hours_schedule"`
|
||||
Count int64 `db:"count" json:"count"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Time time.Time `db:"time" json:"time"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
|
||||
Ip pqtype.Inet `db:"ip" json:"ip"`
|
||||
UserAgent sql.NullString `db:"user_agent" json:"user_agent"`
|
||||
ResourceType ResourceType `db:"resource_type" json:"resource_type"`
|
||||
ResourceID uuid.UUID `db:"resource_id" json:"resource_id"`
|
||||
ResourceTarget string `db:"resource_target" json:"resource_target"`
|
||||
Action AuditAction `db:"action" json:"action"`
|
||||
Diff json.RawMessage `db:"diff" json:"diff"`
|
||||
StatusCode int32 `db:"status_code" json:"status_code"`
|
||||
AdditionalFields json.RawMessage `db:"additional_fields" json:"additional_fields"`
|
||||
RequestID uuid.UUID `db:"request_id" json:"request_id"`
|
||||
ResourceIcon string `db:"resource_icon" json:"resource_icon"`
|
||||
UserUsername sql.NullString `db:"user_username" json:"user_username"`
|
||||
UserName sql.NullString `db:"user_name" json:"user_name"`
|
||||
UserEmail sql.NullString `db:"user_email" json:"user_email"`
|
||||
UserCreatedAt sql.NullTime `db:"user_created_at" json:"user_created_at"`
|
||||
UserUpdatedAt sql.NullTime `db:"user_updated_at" json:"user_updated_at"`
|
||||
UserLastSeenAt sql.NullTime `db:"user_last_seen_at" json:"user_last_seen_at"`
|
||||
UserStatus NullUserStatus `db:"user_status" json:"user_status"`
|
||||
UserLoginType NullLoginType `db:"user_login_type" json:"user_login_type"`
|
||||
UserRoles pq.StringArray `db:"user_roles" json:"user_roles"`
|
||||
UserAvatarUrl sql.NullString `db:"user_avatar_url" json:"user_avatar_url"`
|
||||
UserDeleted sql.NullBool `db:"user_deleted" json:"user_deleted"`
|
||||
UserThemePreference sql.NullString `db:"user_theme_preference" json:"user_theme_preference"`
|
||||
UserQuietHoursSchedule sql.NullString `db:"user_quiet_hours_schedule" json:"user_quiet_hours_schedule"`
|
||||
OrganizationName string `db:"organization_name" json:"organization_name"`
|
||||
OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"`
|
||||
OrganizationIcon string `db:"organization_icon" json:"organization_icon"`
|
||||
Count int64 `db:"count" json:"count"`
|
||||
}
|
||||
|
||||
// GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
|
||||
@@ -667,6 +674,9 @@ func (q *sqlQuerier) GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOff
|
||||
&i.UserDeleted,
|
||||
&i.UserThemePreference,
|
||||
&i.UserQuietHoursSchedule,
|
||||
&i.OrganizationName,
|
||||
&i.OrganizationDisplayName,
|
||||
&i.OrganizationIcon,
|
||||
&i.Count,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -18,6 +18,9 @@ SELECT
|
||||
users.deleted AS user_deleted,
|
||||
users.theme_preference AS user_theme_preference,
|
||||
users.quiet_hours_schedule AS user_quiet_hours_schedule,
|
||||
COALESCE(organizations.name, '') AS organization_name,
|
||||
COALESCE(organizations.display_name, '') AS organization_display_name,
|
||||
COALESCE(organizations.icon, '') AS organization_icon,
|
||||
COUNT(audit_logs.*) OVER () AS count
|
||||
FROM
|
||||
audit_logs
|
||||
@@ -46,6 +49,7 @@ FROM
|
||||
workspaces.id = workspace_builds.workspace_id AND
|
||||
workspace_builds.build_number = 1
|
||||
)
|
||||
LEFT JOIN organizations ON audit_logs.organization_id = organizations.id
|
||||
WHERE
|
||||
-- Filter resource_type
|
||||
CASE
|
||||
|
||||
@@ -315,11 +315,13 @@ func (api *API) deleteOrganization(rw http.ResponseWriter, r *http.Request) {
|
||||
// convertOrganization consumes the database representation and outputs an API friendly representation.
|
||||
func convertOrganization(organization database.Organization) codersdk.Organization {
|
||||
return codersdk.Organization{
|
||||
ID: organization.ID,
|
||||
Name: organization.Name,
|
||||
DisplayName: organization.DisplayName,
|
||||
MinimalOrganization: codersdk.MinimalOrganization{
|
||||
ID: organization.ID,
|
||||
Name: organization.Name,
|
||||
DisplayName: organization.DisplayName,
|
||||
Icon: organization.Icon,
|
||||
},
|
||||
Description: organization.Description,
|
||||
Icon: organization.Icon,
|
||||
CreatedAt: organization.CreatedAt,
|
||||
UpdatedAt: organization.UpdatedAt,
|
||||
IsDefault: organization.IsDefault,
|
||||
|
||||
+12
-8
@@ -125,14 +125,13 @@ type AuditDiffField struct {
|
||||
}
|
||||
|
||||
type AuditLog struct {
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
RequestID uuid.UUID `json:"request_id" format:"uuid"`
|
||||
Time time.Time `json:"time" format:"date-time"`
|
||||
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
|
||||
IP netip.Addr `json:"ip"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
ResourceType ResourceType `json:"resource_type"`
|
||||
ResourceID uuid.UUID `json:"resource_id" format:"uuid"`
|
||||
ID uuid.UUID `json:"id" format:"uuid"`
|
||||
RequestID uuid.UUID `json:"request_id" format:"uuid"`
|
||||
Time time.Time `json:"time" format:"date-time"`
|
||||
IP netip.Addr `json:"ip"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
ResourceType ResourceType `json:"resource_type"`
|
||||
ResourceID uuid.UUID `json:"resource_id" format:"uuid"`
|
||||
// ResourceTarget is the name of the resource.
|
||||
ResourceTarget string `json:"resource_target"`
|
||||
ResourceIcon string `json:"resource_icon"`
|
||||
@@ -144,6 +143,11 @@ type AuditLog struct {
|
||||
ResourceLink string `json:"resource_link"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
|
||||
// Deprecated: Use 'organization.id' instead.
|
||||
OrganizationID uuid.UUID `json:"organization_id" format:"uuid"`
|
||||
|
||||
Organization *MinimalOrganization `json:"organization,omitempty"`
|
||||
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
|
||||
@@ -39,18 +39,22 @@ func ProvisionerTypeValid[T ProvisionerType | string](pt T) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Organization is the JSON representation of a Coder organization.
|
||||
type Organization struct {
|
||||
type MinimalOrganization struct {
|
||||
ID uuid.UUID `table:"id" json:"id" validate:"required" format:"uuid"`
|
||||
Name string `table:"name,default_sort" json:"name"`
|
||||
DisplayName string `table:"display_name" json:"display_name"`
|
||||
Description string `table:"description" json:"description"`
|
||||
CreatedAt time.Time `table:"created_at" json:"created_at" validate:"required" format:"date-time"`
|
||||
UpdatedAt time.Time `table:"updated_at" json:"updated_at" validate:"required" format:"date-time"`
|
||||
IsDefault bool `table:"default" json:"is_default" validate:"required"`
|
||||
Icon string `table:"icon" json:"icon"`
|
||||
}
|
||||
|
||||
// Organization is the JSON representation of a Coder organization.
|
||||
type Organization struct {
|
||||
MinimalOrganization `table:"m,recursive_inline"`
|
||||
Description string `table:"description" json:"description"`
|
||||
CreatedAt time.Time `table:"created_at" json:"created_at" validate:"required" format:"date-time"`
|
||||
UpdatedAt time.Time `table:"updated_at" json:"updated_at" validate:"required" format:"date-time"`
|
||||
IsDefault bool `table:"default" json:"is_default" validate:"required"`
|
||||
}
|
||||
|
||||
func (o Organization) HumanName() string {
|
||||
if o.DisplayName == "" {
|
||||
return o.Name
|
||||
|
||||
Generated
+6
@@ -47,6 +47,12 @@ curl -X GET http://coder-server:8080/api/v2/audit?limit=0 \
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"ip": "string",
|
||||
"is_deleted": true,
|
||||
"organization": {
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string"
|
||||
},
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
|
||||
"resource_icon": "string",
|
||||
|
||||
Generated
+53
-20
@@ -558,6 +558,12 @@
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"ip": "string",
|
||||
"is_deleted": true,
|
||||
"organization": {
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string"
|
||||
},
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
|
||||
"resource_icon": "string",
|
||||
@@ -594,26 +600,27 @@
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------- | ---------------------------------------------- | -------- | ------------ | -------------------------------------------- |
|
||||
| `action` | [codersdk.AuditAction](#codersdkauditaction) | false | | |
|
||||
| `additional_fields` | array of integer | false | | |
|
||||
| `description` | string | false | | |
|
||||
| `diff` | [codersdk.AuditDiff](#codersdkauditdiff) | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `ip` | string | false | | |
|
||||
| `is_deleted` | boolean | false | | |
|
||||
| `organization_id` | string | false | | |
|
||||
| `request_id` | string | false | | |
|
||||
| `resource_icon` | string | false | | |
|
||||
| `resource_id` | string | false | | |
|
||||
| `resource_link` | string | false | | |
|
||||
| `resource_target` | string | false | | Resource target is the name of the resource. |
|
||||
| `resource_type` | [codersdk.ResourceType](#codersdkresourcetype) | false | | |
|
||||
| `status_code` | integer | false | | |
|
||||
| `time` | string | false | | |
|
||||
| `user` | [codersdk.User](#codersdkuser) | false | | |
|
||||
| `user_agent` | string | false | | |
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------- | ------------------------------------------------------------ | -------- | ------------ | -------------------------------------------- |
|
||||
| `action` | [codersdk.AuditAction](#codersdkauditaction) | false | | |
|
||||
| `additional_fields` | array of integer | false | | |
|
||||
| `description` | string | false | | |
|
||||
| `diff` | [codersdk.AuditDiff](#codersdkauditdiff) | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `ip` | string | false | | |
|
||||
| `is_deleted` | boolean | false | | |
|
||||
| `organization` | [codersdk.MinimalOrganization](#codersdkminimalorganization) | false | | |
|
||||
| `organization_id` | string | false | | Deprecated: Use 'organization.id' instead. |
|
||||
| `request_id` | string | false | | |
|
||||
| `resource_icon` | string | false | | |
|
||||
| `resource_id` | string | false | | |
|
||||
| `resource_link` | string | false | | |
|
||||
| `resource_target` | string | false | | Resource target is the name of the resource. |
|
||||
| `resource_type` | [codersdk.ResourceType](#codersdkresourcetype) | false | | |
|
||||
| `status_code` | integer | false | | |
|
||||
| `time` | string | false | | |
|
||||
| `user` | [codersdk.User](#codersdkuser) | false | | |
|
||||
| `user_agent` | string | false | | |
|
||||
|
||||
## codersdk.AuditLogResponse
|
||||
|
||||
@@ -639,6 +646,12 @@
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"ip": "string",
|
||||
"is_deleted": true,
|
||||
"organization": {
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string"
|
||||
},
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"request_id": "266ea41d-adf5-480b-af50-15b940c2b846",
|
||||
"resource_icon": "string",
|
||||
@@ -3078,6 +3091,26 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| --------------- | ------ | -------- | ------------ | ----------- |
|
||||
| `session_token` | string | true | | |
|
||||
|
||||
## codersdk.MinimalOrganization
|
||||
|
||||
```json
|
||||
{
|
||||
"display_name": "string",
|
||||
"icon": "string",
|
||||
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||
"name": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| -------------- | ------ | -------- | ------------ | ----------- |
|
||||
| `display_name` | string | false | | |
|
||||
| `icon` | string | false | | |
|
||||
| `id` | string | true | | |
|
||||
| `name` | string | false | | |
|
||||
|
||||
## codersdk.MinimalUser
|
||||
|
||||
```json
|
||||
|
||||
@@ -114,7 +114,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
|
||||
return xerrors.New("must provide a pre-shared key when not authenticated as a user")
|
||||
}
|
||||
|
||||
org = codersdk.Organization{ID: uuid.Nil}
|
||||
org = codersdk.Organization{MinimalOrganization: codersdk.MinimalOrganization{ID: uuid.Nil}}
|
||||
if orgContext.FlagSelect != "" {
|
||||
// If we are using PSK, we can't fetch the organization
|
||||
// to validate org name so we need the user to provide
|
||||
@@ -123,7 +123,7 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command {
|
||||
if err != nil {
|
||||
return xerrors.New("must provide an org ID when not authenticated as a user and organization is specified")
|
||||
}
|
||||
org = codersdk.Organization{ID: orgID}
|
||||
org = codersdk.Organization{MinimalOrganization: codersdk.MinimalOrganization{ID: orgID}}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Generated
+11
-6
@@ -89,7 +89,6 @@ export interface AuditLog {
|
||||
readonly id: string;
|
||||
readonly request_id: string;
|
||||
readonly time: string;
|
||||
readonly organization_id: string;
|
||||
// Named type "net/netip.Addr" unknown, using "any"
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- External type
|
||||
readonly ip: any;
|
||||
@@ -105,6 +104,8 @@ export interface AuditLog {
|
||||
readonly description: string;
|
||||
readonly resource_link: string;
|
||||
readonly is_deleted: boolean;
|
||||
readonly organization_id: string;
|
||||
readonly organization?: MinimalOrganization;
|
||||
readonly user?: User;
|
||||
}
|
||||
|
||||
@@ -690,6 +691,14 @@ export interface LoginWithPasswordResponse {
|
||||
readonly session_token: string;
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go
|
||||
export interface MinimalOrganization {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly display_name: string;
|
||||
readonly icon: string;
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
export interface MinimalUser {
|
||||
readonly id: string;
|
||||
@@ -844,15 +853,11 @@ export interface OIDCConfig {
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go
|
||||
export interface Organization {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly display_name: string;
|
||||
export interface Organization extends MinimalOrganization {
|
||||
readonly description: string;
|
||||
readonly created_at: string;
|
||||
readonly updated_at: string;
|
||||
readonly is_default: boolean;
|
||||
readonly icon: string;
|
||||
}
|
||||
|
||||
// From codersdk/organizations.go
|
||||
|
||||
@@ -2153,6 +2153,12 @@ export const MockAuditLog: TypesGen.AuditLog = {
|
||||
request_id: "53bded77-7b9d-4e82-8771-991a34d759f9",
|
||||
time: "2022-05-19T16:45:57.122Z",
|
||||
organization_id: MockOrganization.id,
|
||||
organization: {
|
||||
id: MockOrganization.id,
|
||||
name: "mock name",
|
||||
display_name: "mock display name",
|
||||
icon: "/emojis/1f48f-1f3ff.png",
|
||||
},
|
||||
ip: "127.0.0.1",
|
||||
user_agent:
|
||||
'"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"',
|
||||
|
||||
Reference in New Issue
Block a user