mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add disabling of default 'everyone' group access to template (#7982)
* feat: add disabling of default 'everyone' group access to template * add FE to disable everyone group * require entitlement to uncheck box
This commit is contained in:
+13
-5
@@ -28,6 +28,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
|||||||
provisionerTags []string
|
provisionerTags []string
|
||||||
variablesFile string
|
variablesFile string
|
||||||
variables []string
|
variables []string
|
||||||
|
disableEveryone bool
|
||||||
defaultTTL time.Duration
|
defaultTTL time.Duration
|
||||||
failureTTL time.Duration
|
failureTTL time.Duration
|
||||||
inactivityTTL time.Duration
|
inactivityTTL time.Duration
|
||||||
@@ -121,11 +122,12 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createReq := codersdk.CreateTemplateRequest{
|
createReq := codersdk.CreateTemplateRequest{
|
||||||
Name: templateName,
|
Name: templateName,
|
||||||
VersionID: job.ID,
|
VersionID: job.ID,
|
||||||
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
|
DefaultTTLMillis: ptr.Ref(defaultTTL.Milliseconds()),
|
||||||
FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()),
|
FailureTTLMillis: ptr.Ref(failureTTL.Milliseconds()),
|
||||||
InactivityTTLMillis: ptr.Ref(inactivityTTL.Milliseconds()),
|
InactivityTTLMillis: ptr.Ref(inactivityTTL.Milliseconds()),
|
||||||
|
DisableEveryoneGroupAccess: disableEveryone,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = client.CreateTemplate(inv.Context(), organization.ID, createReq)
|
_, err = client.CreateTemplate(inv.Context(), organization.ID, createReq)
|
||||||
@@ -144,6 +146,12 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Options = clibase.OptionSet{
|
cmd.Options = clibase.OptionSet{
|
||||||
|
{
|
||||||
|
Flag: "private",
|
||||||
|
Description: "Disable the default behavior of granting template access to the 'everyone' group. " +
|
||||||
|
"The template permissions must be updated to allow non-admin users to use this template.",
|
||||||
|
Value: clibase.BoolOf(&disableEveryone),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Flag: "variables-file",
|
Flag: "variables-file",
|
||||||
Description: "Specify a file path with values for Terraform-managed variables.",
|
Description: "Specify a file path with values for Terraform-managed variables.",
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ Create a template from the current directory or as specified by flag
|
|||||||
Specify an inactivity TTL for workspaces created from this template.
|
Specify an inactivity TTL for workspaces created from this template.
|
||||||
This licensed feature's default is 0h (off).
|
This licensed feature's default is 0h (off).
|
||||||
|
|
||||||
|
--private bool
|
||||||
|
Disable the default behavior of granting template access to the
|
||||||
|
'everyone' group. The template permissions must be updated to allow
|
||||||
|
non-admin users to use this template.
|
||||||
|
|
||||||
--provisioner-tag string-array
|
--provisioner-tag string-array
|
||||||
Specify a set of tags to target provisioner daemons.
|
Specify a set of tags to target provisioner daemons.
|
||||||
|
|
||||||
|
|||||||
Generated
+4
@@ -6664,6 +6664,10 @@ const docTemplate = `{
|
|||||||
"description": "Description is a description of what the template contains. It must be\nless than 128 bytes.",
|
"description": "Description is a description of what the template contains. It must be\nless than 128 bytes.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"disable_everyone_group_access": {
|
||||||
|
"description": "DisableEveryoneGroupAccess allows optionally disabling the default\nbehavior of granting the 'everyone' group access to use the template.\nIf this is set to true, the template will not be available to all users,\nand must be explicitly granted to users or groups in the permissions settings\nof the template.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"display_name": {
|
"display_name": {
|
||||||
"description": "DisplayName is the displayed name of the template.",
|
"description": "DisplayName is the displayed name of the template.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
Generated
+4
@@ -5935,6 +5935,10 @@
|
|||||||
"description": "Description is a description of what the template contains. It must be\nless than 128 bytes.",
|
"description": "Description is a description of what the template contains. It must be\nless than 128 bytes.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"disable_everyone_group_access": {
|
||||||
|
"description": "DisableEveryoneGroupAccess allows optionally disabling the default\nbehavior of granting the 'everyone' group access to use the template.\nIf this is set to true, the template will not be available to all users,\nand must be explicitly granted to users or groups in the permissions settings\nof the template.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"display_name": {
|
"display_name": {
|
||||||
"description": "DisplayName is the displayed name of the template.",
|
"description": "DisplayName is the displayed name of the template.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
@@ -42,6 +42,14 @@ type MockAuditor struct {
|
|||||||
auditLogs []database.AuditLog
|
auditLogs []database.AuditLog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetLogs removes all audit logs from the mock auditor.
|
||||||
|
// This is helpful for testing to get a clean slate.
|
||||||
|
func (a *MockAuditor) ResetLogs() {
|
||||||
|
a.mutex.Lock()
|
||||||
|
defer a.mutex.Unlock()
|
||||||
|
a.auditLogs = make([]database.AuditLog, 0)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *MockAuditor) AuditLogs() []database.AuditLog {
|
func (a *MockAuditor) AuditLogs() []database.AuditLog {
|
||||||
a.mutex.Lock()
|
a.mutex.Lock()
|
||||||
defer a.mutex.Unlock()
|
defer a.mutex.Unlock()
|
||||||
|
|||||||
+17
-13
@@ -274,22 +274,26 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
|||||||
allowUserAutostop = ptr.NilToDefault(createTemplate.AllowUserAutostop, true)
|
allowUserAutostop = ptr.NilToDefault(createTemplate.AllowUserAutostop, true)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
defaultsGroups := database.TemplateACL{}
|
||||||
|
if !createTemplate.DisableEveryoneGroupAccess {
|
||||||
|
// The organization ID is used as the group ID for the everyone group
|
||||||
|
// in this organization.
|
||||||
|
defaultsGroups[organization.ID.String()] = []rbac.Action{rbac.ActionRead}
|
||||||
|
}
|
||||||
err = api.Database.InTx(func(tx database.Store) error {
|
err = api.Database.InTx(func(tx database.Store) error {
|
||||||
now := database.Now()
|
now := database.Now()
|
||||||
dbTemplate, err = tx.InsertTemplate(ctx, database.InsertTemplateParams{
|
dbTemplate, err = tx.InsertTemplate(ctx, database.InsertTemplateParams{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
OrganizationID: organization.ID,
|
OrganizationID: organization.ID,
|
||||||
Name: createTemplate.Name,
|
Name: createTemplate.Name,
|
||||||
Provisioner: importJob.Provisioner,
|
Provisioner: importJob.Provisioner,
|
||||||
ActiveVersionID: templateVersion.ID,
|
ActiveVersionID: templateVersion.ID,
|
||||||
Description: createTemplate.Description,
|
Description: createTemplate.Description,
|
||||||
CreatedBy: apiKey.UserID,
|
CreatedBy: apiKey.UserID,
|
||||||
UserACL: database.TemplateACL{},
|
UserACL: database.TemplateACL{},
|
||||||
GroupACL: database.TemplateACL{
|
GroupACL: defaultsGroups,
|
||||||
organization.ID.String(): []rbac.Action{rbac.ActionRead},
|
|
||||||
},
|
|
||||||
DisplayName: createTemplate.DisplayName,
|
DisplayName: createTemplate.DisplayName,
|
||||||
Icon: createTemplate.Icon,
|
Icon: createTemplate.Icon,
|
||||||
AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs,
|
AllowUserCancelWorkspaceJobs: allowUserCancelWorkspaceJobs,
|
||||||
|
|||||||
@@ -48,25 +48,28 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
auditor := audit.NewMock()
|
auditor := audit.NewMock()
|
||||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
|
||||||
user := coderdtest.CreateFirstUser(t, client)
|
owner := coderdtest.CreateFirstUser(t, client)
|
||||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
// By default, everyone in the org can read the template.
|
||||||
|
user, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||||
|
auditor.ResetLogs()
|
||||||
|
|
||||||
expected := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||||
|
|
||||||
|
expected := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
got, err := client.Template(ctx, expected.ID)
|
got, err := user.Template(ctx, expected.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, expected.Name, got.Name)
|
assert.Equal(t, expected.Name, got.Name)
|
||||||
assert.Equal(t, expected.Description, got.Description)
|
assert.Equal(t, expected.Description, got.Description)
|
||||||
|
|
||||||
require.Len(t, auditor.AuditLogs(), 4)
|
require.Len(t, auditor.AuditLogs(), 3)
|
||||||
assert.Equal(t, database.AuditActionLogin, auditor.AuditLogs()[0].Action)
|
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[0].Action)
|
||||||
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[1].Action)
|
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs()[1].Action)
|
||||||
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs()[2].Action)
|
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[2].Action)
|
||||||
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[3].Action)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("AlreadyExists", func(t *testing.T) {
|
t.Run("AlreadyExists", func(t *testing.T) {
|
||||||
@@ -126,6 +129,27 @@ func TestPostTemplateByOrganization(t *testing.T) {
|
|||||||
require.Zero(t, got.DefaultTTLMillis)
|
require.Zero(t, got.DefaultTTLMillis)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("DisableEveryone", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
auditor := audit.NewMock()
|
||||||
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
|
||||||
|
owner := coderdtest.CreateFirstUser(t, client)
|
||||||
|
user, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||||
|
|
||||||
|
expected := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID, func(request *codersdk.CreateTemplateRequest) {
|
||||||
|
request.DisableEveryoneGroupAccess = true
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := user.Template(ctx, expected.ID)
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Unauthorized", func(t *testing.T) {
|
t.Run("Unauthorized", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
client := coderdtest.New(t, nil)
|
client := coderdtest.New(t, nil)
|
||||||
|
|||||||
@@ -108,6 +108,13 @@ type CreateTemplateRequest struct {
|
|||||||
// InactivityTTLMillis allows optionally specifying the max lifetime before Coder
|
// InactivityTTLMillis allows optionally specifying the max lifetime before Coder
|
||||||
// deletes inactive workspaces created from this template.
|
// deletes inactive workspaces created from this template.
|
||||||
InactivityTTLMillis *int64 `json:"inactivity_ttl_ms,omitempty"`
|
InactivityTTLMillis *int64 `json:"inactivity_ttl_ms,omitempty"`
|
||||||
|
|
||||||
|
// DisableEveryoneGroupAccess allows optionally disabling the default
|
||||||
|
// behavior of granting the 'everyone' group access to use the template.
|
||||||
|
// If this is set to true, the template will not be available to all users,
|
||||||
|
// and must be explicitly granted to users or groups in the permissions settings
|
||||||
|
// of the template.
|
||||||
|
DisableEveryoneGroupAccess bool `json:"disable_everyone_group_access"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateWorkspaceRequest provides options for creating a new workspace.
|
// CreateWorkspaceRequest provides options for creating a new workspace.
|
||||||
|
|||||||
+16
-14
@@ -1343,6 +1343,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||||||
"allow_user_cancel_workspace_jobs": true,
|
"allow_user_cancel_workspace_jobs": true,
|
||||||
"default_ttl_ms": 0,
|
"default_ttl_ms": 0,
|
||||||
"description": "string",
|
"description": "string",
|
||||||
|
"disable_everyone_group_access": true,
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"failure_ttl_ms": 0,
|
"failure_ttl_ms": 0,
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
@@ -1355,20 +1356,21 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
| Name | Type | Required | Restrictions | Description |
|
| Name | Type | Required | Restrictions | Description |
|
||||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `allow_user_autostart` | boolean | false | | Allow user autostart allows users to set a schedule for autostarting their workspace. By default this is true. This can only be disabled when using an enterprise license. |
|
| `allow_user_autostart` | boolean | false | | Allow user autostart allows users to set a schedule for autostarting their workspace. By default this is true. This can only be disabled when using an enterprise license. |
|
||||||
| `allow_user_autostop` | boolean | false | | Allow user autostop allows users to set a custom workspace TTL to use in place of the template's DefaultTTL field. By default this is true. If false, the DefaultTTL will always be used. This can only be disabled when using an enterprise license. |
|
| `allow_user_autostop` | boolean | false | | Allow user autostop allows users to set a custom workspace TTL to use in place of the template's DefaultTTL field. By default this is true. If false, the DefaultTTL will always be used. This can only be disabled when using an enterprise license. |
|
||||||
| `allow_user_cancel_workspace_jobs` | boolean | false | | Allow users to cancel in-progress workspace jobs. \*bool as the default value is "true". |
|
| `allow_user_cancel_workspace_jobs` | boolean | false | | Allow users to cancel in-progress workspace jobs. \*bool as the default value is "true". |
|
||||||
| `default_ttl_ms` | integer | false | | Default ttl ms allows optionally specifying the default TTL for all workspaces created from this template. |
|
| `default_ttl_ms` | integer | false | | Default ttl ms allows optionally specifying the default TTL for all workspaces created from this template. |
|
||||||
| `description` | string | false | | Description is a description of what the template contains. It must be less than 128 bytes. |
|
| `description` | string | false | | Description is a description of what the template contains. It must be less than 128 bytes. |
|
||||||
| `display_name` | string | false | | Display name is the displayed name of the template. |
|
| `disable_everyone_group_access` | boolean | false | | Disable everyone group access allows optionally disabling the default behavior of granting the 'everyone' group access to use the template. If this is set to true, the template will not be available to all users, and must be explicitly granted to users or groups in the permissions settings of the template. |
|
||||||
| `failure_ttl_ms` | integer | false | | Failure ttl ms allows optionally specifying the max lifetime before Coder stops all resources for failed workspaces created from this template. |
|
| `display_name` | string | false | | Display name is the displayed name of the template. |
|
||||||
| `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
|
| `failure_ttl_ms` | integer | false | | Failure ttl ms allows optionally specifying the max lifetime before Coder stops all resources for failed workspaces created from this template. |
|
||||||
| `inactivity_ttl_ms` | integer | false | | Inactivity ttl ms allows optionally specifying the max lifetime before Coder deletes inactive workspaces created from this template. |
|
| `icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
|
||||||
| `max_ttl_ms` | integer | false | | Max ttl ms allows optionally specifying the max lifetime for workspaces created from this template. |
|
| `inactivity_ttl_ms` | integer | false | | Inactivity ttl ms allows optionally specifying the max lifetime before Coder deletes inactive workspaces created from this template. |
|
||||||
| `name` | string | true | | Name is the name of the template. |
|
| `max_ttl_ms` | integer | false | | Max ttl ms allows optionally specifying the max lifetime for workspaces created from this template. |
|
||||||
| `template_version_id` | string | true | | Template version ID is an in-progress or completed job to use as an initial version of the template. |
|
| `name` | string | true | | Name is the name of the template. |
|
||||||
|
| `template_version_id` | string | true | | Template version ID is an in-progress or completed job to use as an initial version of the template. |
|
||||||
| This is required on creation to enable a user-flow of validating a template works. There is no reason the data-model cannot support empty templates, but it doesn't make sense for users. |
|
| This is required on creation to enable a user-flow of validating a template works. There is no reason the data-model cannot support empty templates, but it doesn't make sense for users. |
|
||||||
|
|
||||||
## codersdk.CreateTemplateVersionDryRunRequest
|
## codersdk.CreateTemplateVersionDryRunRequest
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
|
|||||||
"allow_user_cancel_workspace_jobs": true,
|
"allow_user_cancel_workspace_jobs": true,
|
||||||
"default_ttl_ms": 0,
|
"default_ttl_ms": 0,
|
||||||
"description": "string",
|
"description": "string",
|
||||||
|
"disable_everyone_group_access": true,
|
||||||
"display_name": "string",
|
"display_name": "string",
|
||||||
"failure_ttl_ms": 0,
|
"failure_ttl_ms": 0,
|
||||||
"icon": "string",
|
"icon": "string",
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ Specify a failure TTL for workspaces created from this template. This licensed f
|
|||||||
|
|
||||||
Specify an inactivity TTL for workspaces created from this template. This licensed feature's default is 0h (off).
|
Specify an inactivity TTL for workspaces created from this template. This licensed feature's default is 0h (off).
|
||||||
|
|
||||||
|
### --private
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| ---- | ----------------- |
|
||||||
|
| Type | <code>bool</code> |
|
||||||
|
|
||||||
|
Disable the default behavior of granting template access to the 'everyone' group. The template permissions must be updated to allow non-admin users to use this template.
|
||||||
|
|
||||||
### --provisioner-tag
|
### --provisioner-tag
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ export interface CreateTemplateRequest {
|
|||||||
readonly allow_user_autostop?: boolean
|
readonly allow_user_autostop?: boolean
|
||||||
readonly failure_ttl_ms?: number
|
readonly failure_ttl_ms?: number
|
||||||
readonly inactivity_ttl_ms?: number
|
readonly inactivity_ttl_ms?: number
|
||||||
|
readonly disable_everyone_group_access: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/templateversions.go
|
// From codersdk/templateversions.go
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export default {
|
|||||||
component: CreateTemplateForm,
|
component: CreateTemplateForm,
|
||||||
args: {
|
args: {
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
|
allowDisableEveryoneAccess: true,
|
||||||
},
|
},
|
||||||
} as ComponentMeta<typeof CreateTemplateForm>
|
} as ComponentMeta<typeof CreateTemplateForm>
|
||||||
|
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ const defaultInitialValues: CreateTemplateData = {
|
|||||||
allow_user_cancel_workspace_jobs: false,
|
allow_user_cancel_workspace_jobs: false,
|
||||||
allow_user_autostart: false,
|
allow_user_autostart: false,
|
||||||
allow_user_autostop: false,
|
allow_user_autostop: false,
|
||||||
|
allow_everyone_group_access: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetInitialValuesParams = {
|
type GetInitialValuesParams = {
|
||||||
@@ -174,6 +175,7 @@ export interface CreateTemplateFormProps {
|
|||||||
logs?: ProvisionerJobLog[]
|
logs?: ProvisionerJobLog[]
|
||||||
allowAdvancedScheduling: boolean
|
allowAdvancedScheduling: boolean
|
||||||
copiedTemplate?: Template
|
copiedTemplate?: Template
|
||||||
|
allowDisableEveryoneAccess: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
|
export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
|
||||||
@@ -188,6 +190,7 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
|
|||||||
jobError,
|
jobError,
|
||||||
logs,
|
logs,
|
||||||
allowAdvancedScheduling,
|
allowAdvancedScheduling,
|
||||||
|
allowDisableEveryoneAccess,
|
||||||
}) => {
|
}) => {
|
||||||
const styles = useStyles()
|
const styles = useStyles()
|
||||||
const form = useFormik<CreateTemplateData>({
|
const form = useFormik<CreateTemplateData>({
|
||||||
@@ -379,44 +382,90 @@ export const CreateTemplateForm: FC<CreateTemplateFormProps> = ({
|
|||||||
</FormFields>
|
</FormFields>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
{/* Operations */}
|
{/* Permissions */}
|
||||||
<FormSection
|
<FormSection
|
||||||
title="Operations"
|
title="Permissions"
|
||||||
description="Regulate actions allowed on workspaces created from this template."
|
description="Regulate actions allowed on workspaces created from this template."
|
||||||
>
|
>
|
||||||
<FormFields>
|
<Stack direction="column">
|
||||||
<label htmlFor="allow_user_cancel_workspace_jobs">
|
<FormFields>
|
||||||
<Stack direction="row" spacing={1}>
|
<label htmlFor="allow_user_cancel_workspace_jobs">
|
||||||
<Checkbox
|
<Stack direction="row" spacing={1}>
|
||||||
id="allow_user_cancel_workspace_jobs"
|
<Checkbox
|
||||||
name="allow_user_cancel_workspace_jobs"
|
id="allow_user_cancel_workspace_jobs"
|
||||||
disabled={isSubmitting}
|
name="allow_user_cancel_workspace_jobs"
|
||||||
checked={form.values.allow_user_cancel_workspace_jobs}
|
disabled={isSubmitting}
|
||||||
onChange={form.handleChange}
|
checked={form.values.allow_user_cancel_workspace_jobs}
|
||||||
/>
|
onChange={form.handleChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<Stack direction="column" spacing={0.5}>
|
<Stack direction="column" spacing={0.5}>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
spacing={0.5}
|
spacing={0.5}
|
||||||
className={styles.optionText}
|
className={styles.optionText}
|
||||||
>
|
>
|
||||||
<strong>{t("form.fields.allowUsersToCancel")}</strong>
|
<strong>{t("form.fields.allowUsersToCancel")}</strong>
|
||||||
|
|
||||||
<HelpTooltip>
|
<HelpTooltip>
|
||||||
<HelpTooltipText>
|
<HelpTooltipText>
|
||||||
{t("form.tooltip.allowUsersToCancel")}
|
{t("form.tooltip.allowUsersToCancel")}
|
||||||
</HelpTooltipText>
|
</HelpTooltipText>
|
||||||
</HelpTooltip>
|
</HelpTooltip>
|
||||||
|
</Stack>
|
||||||
|
<span className={styles.optionHelperText}>
|
||||||
|
{t("form.helperText.allowUsersToCancel")}
|
||||||
|
</span>
|
||||||
</Stack>
|
</Stack>
|
||||||
<span className={styles.optionHelperText}>
|
|
||||||
{t("form.helperText.allowUsersToCancel")}
|
|
||||||
</span>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</label>
|
||||||
</label>
|
</FormFields>
|
||||||
</FormFields>
|
<FormFields>
|
||||||
|
<label htmlFor="allow_everyone_group_access">
|
||||||
|
<Stack direction="row" spacing={1}>
|
||||||
|
<Checkbox
|
||||||
|
id="allow_everyone_group_access"
|
||||||
|
name="allow_everyone_group_access"
|
||||||
|
disabled={isSubmitting || !allowDisableEveryoneAccess}
|
||||||
|
checked={form.values.allow_everyone_group_access}
|
||||||
|
onChange={form.handleChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Stack direction="column" spacing={0.5}>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
spacing={0.5}
|
||||||
|
className={styles.optionText}
|
||||||
|
>
|
||||||
|
<strong>Allow everyone to use the template</strong>
|
||||||
|
|
||||||
|
<HelpTooltip>
|
||||||
|
<HelpTooltipText>
|
||||||
|
If unchecked, only users with the 'template
|
||||||
|
admin' and 'owner' role can use this
|
||||||
|
template until the permissions are updated. Navigate to{" "}
|
||||||
|
<strong>
|
||||||
|
Templates > Select a template > Settings >
|
||||||
|
Permissions
|
||||||
|
</strong>{" "}
|
||||||
|
to update permissions.
|
||||||
|
</HelpTooltipText>
|
||||||
|
</HelpTooltip>
|
||||||
|
</Stack>
|
||||||
|
<span className={styles.optionHelperText}>
|
||||||
|
This setting requires an enterprise license for the
|
||||||
|
<Link href="https://coder.com/docs/v2/latest/admin/rbac">
|
||||||
|
'Template RBAC'
|
||||||
|
</Link>{" "}
|
||||||
|
feature to customize permissions.
|
||||||
|
</span>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</label>
|
||||||
|
</FormFields>
|
||||||
|
</Stack>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
|
|
||||||
{/* Variables */}
|
{/* Variables */}
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ const CreateTemplatePage: FC = () => {
|
|||||||
const { entitlements } = useDashboard()
|
const { entitlements } = useDashboard()
|
||||||
const allowAdvancedScheduling =
|
const allowAdvancedScheduling =
|
||||||
entitlements.features["advanced_template_scheduling"].enabled
|
entitlements.features["advanced_template_scheduling"].enabled
|
||||||
|
// Requires the template RBAC feature, otherwise disabling everyone access
|
||||||
|
// means no one can access.
|
||||||
|
const allowDisableEveryoneAccess =
|
||||||
|
entitlements.features["template_rbac"].enabled
|
||||||
|
|
||||||
const onCancel = () => {
|
const onCancel = () => {
|
||||||
navigate(-1)
|
navigate(-1)
|
||||||
@@ -64,6 +68,7 @@ const CreateTemplatePage: FC = () => {
|
|||||||
<CreateTemplateForm
|
<CreateTemplateForm
|
||||||
copiedTemplate={state.context.copiedTemplate}
|
copiedTemplate={state.context.copiedTemplate}
|
||||||
allowAdvancedScheduling={allowAdvancedScheduling}
|
allowAdvancedScheduling={allowAdvancedScheduling}
|
||||||
|
allowDisableEveryoneAccess={allowDisableEveryoneAccess}
|
||||||
error={error}
|
error={error}
|
||||||
starterTemplate={starterTemplate}
|
starterTemplate={starterTemplate}
|
||||||
isSubmitting={state.hasTag("submitting")}
|
isSubmitting={state.hasTag("submitting")}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export interface CreateTemplateData {
|
|||||||
allow_user_cancel_workspace_jobs: boolean
|
allow_user_cancel_workspace_jobs: boolean
|
||||||
parameter_values_by_name?: Record<string, string>
|
parameter_values_by_name?: Record<string, string>
|
||||||
user_variable_values?: VariableValue[]
|
user_variable_values?: VariableValue[]
|
||||||
|
allow_everyone_group_access: boolean
|
||||||
}
|
}
|
||||||
interface CreateTemplateContext {
|
interface CreateTemplateContext {
|
||||||
organizationId: string
|
organizationId: string
|
||||||
@@ -457,11 +458,13 @@ export const createTemplateMachine =
|
|||||||
default_ttl_hours,
|
default_ttl_hours,
|
||||||
max_ttl_hours,
|
max_ttl_hours,
|
||||||
parameter_values_by_name,
|
parameter_values_by_name,
|
||||||
|
allow_everyone_group_access,
|
||||||
...safeTemplateData
|
...safeTemplateData
|
||||||
} = templateData
|
} = templateData
|
||||||
|
|
||||||
return createTemplate(organizationId, {
|
return createTemplate(organizationId, {
|
||||||
...safeTemplateData,
|
...safeTemplateData,
|
||||||
|
disable_everyone_group_access: !allow_everyone_group_access,
|
||||||
default_ttl_ms: templateData.default_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
|
default_ttl_ms: templateData.default_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
|
||||||
max_ttl_ms: templateData.max_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
|
max_ttl_ms: templateData.max_ttl_hours * 60 * 60 * 1000, // Convert hours to ms
|
||||||
template_version_id: version.id,
|
template_version_id: version.id,
|
||||||
|
|||||||
Reference in New Issue
Block a user