mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
chore: remove UpsertCustomRole in favor of Insert + Update (#14217)
* chore: remove UpsertCustomRole in favor of Insert + Update --------- Co-authored-by: Jaayden Halko <jaayden.halko@gmail.com>
This commit is contained in:
@ -19,8 +19,8 @@ import (
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
// TestUpsertCustomRoles verifies creating custom roles cannot escalate permissions.
|
||||
func TestUpsertCustomRoles(t *testing.T) {
|
||||
// TestInsertCustomRoles verifies creating custom roles cannot escalate permissions.
|
||||
func TestInsertCustomRoles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
userID := uuid.New()
|
||||
@ -98,7 +98,7 @@ func TestUpsertCustomRoles(t *testing.T) {
|
||||
org: codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
codersdk.ResourceWorkspace: {codersdk.ActionRead},
|
||||
}),
|
||||
errorContains: "cannot assign both org and site permissions",
|
||||
errorContains: "organization roles specify site or user permissions",
|
||||
},
|
||||
{
|
||||
name: "invalid-action",
|
||||
@ -231,7 +231,7 @@ func TestUpsertCustomRoles(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
ctx = dbauthz.As(ctx, subject)
|
||||
|
||||
_, err := az.UpsertCustomRole(ctx, database.UpsertCustomRoleParams{
|
||||
_, err := az.InsertCustomRole(ctx, database.InsertCustomRoleParams{
|
||||
Name: "test-role",
|
||||
DisplayName: "",
|
||||
OrganizationID: tc.organizationID,
|
||||
|
@ -815,6 +815,86 @@ func (q *querier) customRoleEscalationCheck(ctx context.Context, actor rbac.Subj
|
||||
return nil
|
||||
}
|
||||
|
||||
// customRoleCheck will validate a custom role for inserting or updating.
|
||||
// If the role is not valid, an error will be returned.
|
||||
// - Check custom roles are valid for their resource types + actions
|
||||
// - Check the actor can create the custom role
|
||||
// - Check the custom role does not grant perms the actor does not have
|
||||
// - Prevent negative perms
|
||||
// - Prevent roles with site and org permissions.
|
||||
func (q *querier) customRoleCheck(ctx context.Context, role database.CustomRole) error {
|
||||
act, ok := ActorFromContext(ctx)
|
||||
if !ok {
|
||||
return NoActorError
|
||||
}
|
||||
|
||||
// Org permissions require an org role
|
||||
if role.OrganizationID.UUID == uuid.Nil && len(role.OrgPermissions) > 0 {
|
||||
return xerrors.Errorf("organization permissions require specifying an organization id")
|
||||
}
|
||||
|
||||
// Org roles can only specify org permissions
|
||||
if role.OrganizationID.UUID != uuid.Nil && (len(role.SitePermissions) > 0 || len(role.UserPermissions) > 0) {
|
||||
return xerrors.Errorf("organization roles specify site or user permissions")
|
||||
}
|
||||
|
||||
// The rbac.Role has a 'Valid()' function on it that will do a lot
|
||||
// of checks.
|
||||
rbacRole, err := rolestore.ConvertDBRole(database.CustomRole{
|
||||
Name: role.Name,
|
||||
DisplayName: role.DisplayName,
|
||||
SitePermissions: role.SitePermissions,
|
||||
OrgPermissions: role.OrgPermissions,
|
||||
UserPermissions: role.UserPermissions,
|
||||
OrganizationID: role.OrganizationID,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("invalid args: %w", err)
|
||||
}
|
||||
|
||||
err = rbacRole.Valid()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("invalid role: %w", err)
|
||||
}
|
||||
|
||||
if len(rbacRole.Org) > 0 && len(rbacRole.Site) > 0 {
|
||||
// This is a choice to keep roles simple. If we allow mixing site and org scoped perms, then knowing who can
|
||||
// do what gets more complicated.
|
||||
return xerrors.Errorf("invalid custom role, cannot assign both org and site permissions at the same time")
|
||||
}
|
||||
|
||||
if len(rbacRole.Org) > 1 {
|
||||
// Again to avoid more complexity in our roles
|
||||
return xerrors.Errorf("invalid custom role, cannot assign permissions to more than 1 org at a time")
|
||||
}
|
||||
|
||||
// Prevent escalation
|
||||
for _, sitePerm := range rbacRole.Site {
|
||||
err := q.customRoleEscalationCheck(ctx, act, sitePerm, rbac.Object{Type: sitePerm.ResourceType})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("site permission: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for orgID, perms := range rbacRole.Org {
|
||||
for _, orgPerm := range perms {
|
||||
err := q.customRoleEscalationCheck(ctx, act, orgPerm, rbac.Object{OrgID: orgID, Type: orgPerm.ResourceType})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("org=%q: %w", orgID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, userPerm := range rbacRole.User {
|
||||
err := q.customRoleEscalationCheck(ctx, act, userPerm, rbac.Object{Type: userPerm.ResourceType, Owner: act.ID})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("user permission: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *querier) AcquireLock(ctx context.Context, id int64) error {
|
||||
return q.db.AcquireLock(ctx, id)
|
||||
}
|
||||
@ -2551,6 +2631,34 @@ func (q *querier) InsertAuditLog(ctx context.Context, arg database.InsertAuditLo
|
||||
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertAuditLog)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertCustomRole(ctx context.Context, arg database.InsertCustomRoleParams) (database.CustomRole, error) {
|
||||
// Org and site role upsert share the same query. So switch the assertion based on the org uuid.
|
||||
if arg.OrganizationID.UUID != uuid.Nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
} else {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceAssignRole); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := q.customRoleCheck(ctx, database.CustomRole{
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
SitePermissions: arg.SitePermissions,
|
||||
OrgPermissions: arg.OrgPermissions,
|
||||
UserPermissions: arg.UserPermissions,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
OrganizationID: arg.OrganizationID,
|
||||
ID: uuid.New(),
|
||||
}); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
return q.db.InsertCustomRole(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertDBCryptKey(ctx context.Context, arg database.InsertDBCryptKeyParams) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
@ -3002,6 +3110,33 @@ func (q *querier) UpdateAPIKeyByID(ctx context.Context, arg database.UpdateAPIKe
|
||||
return update(q.log, q.auth, fetch, q.db.UpdateAPIKeyByID)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateCustomRole(ctx context.Context, arg database.UpdateCustomRoleParams) (database.CustomRole, error) {
|
||||
if arg.OrganizationID.UUID != uuid.Nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
} else {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceAssignRole); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := q.customRoleCheck(ctx, database.CustomRole{
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
SitePermissions: arg.SitePermissions,
|
||||
OrgPermissions: arg.OrgPermissions,
|
||||
UserPermissions: arg.UserPermissions,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
OrganizationID: arg.OrganizationID,
|
||||
ID: uuid.New(),
|
||||
}); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
return q.db.UpdateCustomRole(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateExternalAuthLink(ctx context.Context, arg database.UpdateExternalAuthLinkParams) (database.ExternalAuthLink, error) {
|
||||
fetch := func(ctx context.Context, arg database.UpdateExternalAuthLinkParams) (database.ExternalAuthLink, error) {
|
||||
return q.db.GetExternalAuthLink(ctx, database.GetExternalAuthLinkParams{UserID: arg.UserID, ProviderID: arg.ProviderID})
|
||||
@ -3664,91 +3799,6 @@ func (q *querier) UpsertApplicationName(ctx context.Context, value string) error
|
||||
return q.db.UpsertApplicationName(ctx, value)
|
||||
}
|
||||
|
||||
// UpsertCustomRole does a series of authz checks to protect custom roles.
|
||||
// - Check custom roles are valid for their resource types + actions
|
||||
// - Check the actor can create the custom role
|
||||
// - Check the custom role does not grant perms the actor does not have
|
||||
// - Prevent negative perms
|
||||
// - Prevent roles with site and org permissions.
|
||||
func (q *querier) UpsertCustomRole(ctx context.Context, arg database.UpsertCustomRoleParams) (database.CustomRole, error) {
|
||||
act, ok := ActorFromContext(ctx)
|
||||
if !ok {
|
||||
return database.CustomRole{}, NoActorError
|
||||
}
|
||||
|
||||
// Org and site role upsert share the same query. So switch the assertion based on the org uuid.
|
||||
if arg.OrganizationID.UUID != uuid.Nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
} else {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceAssignRole); err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if arg.OrganizationID.UUID == uuid.Nil && len(arg.OrgPermissions) > 0 {
|
||||
return database.CustomRole{}, xerrors.Errorf("organization permissions require specifying an organization id")
|
||||
}
|
||||
|
||||
// There is quite a bit of validation we should do here.
|
||||
// The rbac.Role has a 'Valid()' function on it that will do a lot
|
||||
// of checks.
|
||||
rbacRole, err := rolestore.ConvertDBRole(database.CustomRole{
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
SitePermissions: arg.SitePermissions,
|
||||
OrgPermissions: arg.OrgPermissions,
|
||||
UserPermissions: arg.UserPermissions,
|
||||
OrganizationID: arg.OrganizationID,
|
||||
})
|
||||
if err != nil {
|
||||
return database.CustomRole{}, xerrors.Errorf("invalid args: %w", err)
|
||||
}
|
||||
|
||||
err = rbacRole.Valid()
|
||||
if err != nil {
|
||||
return database.CustomRole{}, xerrors.Errorf("invalid role: %w", err)
|
||||
}
|
||||
|
||||
if len(rbacRole.Org) > 0 && len(rbacRole.Site) > 0 {
|
||||
// This is a choice to keep roles simple. If we allow mixing site and org scoped perms, then knowing who can
|
||||
// do what gets more complicated.
|
||||
return database.CustomRole{}, xerrors.Errorf("invalid custom role, cannot assign both org and site permissions at the same time")
|
||||
}
|
||||
|
||||
if len(rbacRole.Org) > 1 {
|
||||
// Again to avoid more complexity in our roles
|
||||
return database.CustomRole{}, xerrors.Errorf("invalid custom role, cannot assign permissions to more than 1 org at a time")
|
||||
}
|
||||
|
||||
// Prevent escalation
|
||||
for _, sitePerm := range rbacRole.Site {
|
||||
err := q.customRoleEscalationCheck(ctx, act, sitePerm, rbac.Object{Type: sitePerm.ResourceType})
|
||||
if err != nil {
|
||||
return database.CustomRole{}, xerrors.Errorf("site permission: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
for orgID, perms := range rbacRole.Org {
|
||||
for _, orgPerm := range perms {
|
||||
err := q.customRoleEscalationCheck(ctx, act, orgPerm, rbac.Object{OrgID: orgID, Type: orgPerm.ResourceType})
|
||||
if err != nil {
|
||||
return database.CustomRole{}, xerrors.Errorf("org=%q: %w", orgID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, userPerm := range rbacRole.User {
|
||||
err := q.customRoleEscalationCheck(ctx, act, userPerm, rbac.Object{Type: userPerm.ResourceType, Owner: act.ID})
|
||||
if err != nil {
|
||||
return database.CustomRole{}, xerrors.Errorf("user permission: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return q.db.UpsertCustomRole(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertDefaultProxy(ctx context.Context, arg database.UpsertDefaultProxyParams) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
|
@ -1282,9 +1282,77 @@ func (s *MethodTestSuite) TestUser() {
|
||||
}).Asserts(
|
||||
rbac.ResourceAssignRole, policy.ActionDelete)
|
||||
}))
|
||||
s.Run("Blank/UpsertCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
s.Run("Blank/UpdateCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
customRole := dbgen.CustomRole(s.T(), db, database.CustomRole{})
|
||||
// Blank is no perms in the role
|
||||
check.Args(database.UpsertCustomRoleParams{
|
||||
check.Args(database.UpdateCustomRoleParams{
|
||||
Name: customRole.Name,
|
||||
DisplayName: "Test Name",
|
||||
SitePermissions: nil,
|
||||
OrgPermissions: nil,
|
||||
UserPermissions: nil,
|
||||
}).Asserts(rbac.ResourceAssignRole, policy.ActionUpdate)
|
||||
}))
|
||||
s.Run("SitePermissions/UpdateCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
customRole := dbgen.CustomRole(s.T(), db, database.CustomRole{
|
||||
OrganizationID: uuid.NullUUID{
|
||||
UUID: uuid.Nil,
|
||||
Valid: false,
|
||||
},
|
||||
})
|
||||
check.Args(database.UpdateCustomRoleParams{
|
||||
Name: customRole.Name,
|
||||
OrganizationID: customRole.OrganizationID,
|
||||
DisplayName: "Test Name",
|
||||
SitePermissions: db2sdk.List(codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
codersdk.ResourceTemplate: {codersdk.ActionCreate, codersdk.ActionRead, codersdk.ActionUpdate, codersdk.ActionDelete, codersdk.ActionViewInsights},
|
||||
}), convertSDKPerm),
|
||||
OrgPermissions: nil,
|
||||
UserPermissions: db2sdk.List(codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
codersdk.ResourceWorkspace: {codersdk.ActionRead},
|
||||
}), convertSDKPerm),
|
||||
}).Asserts(
|
||||
// First check
|
||||
rbac.ResourceAssignRole, policy.ActionUpdate,
|
||||
// Escalation checks
|
||||
rbac.ResourceTemplate, policy.ActionCreate,
|
||||
rbac.ResourceTemplate, policy.ActionRead,
|
||||
rbac.ResourceTemplate, policy.ActionUpdate,
|
||||
rbac.ResourceTemplate, policy.ActionDelete,
|
||||
rbac.ResourceTemplate, policy.ActionViewInsights,
|
||||
|
||||
rbac.ResourceWorkspace.WithOwner(testActorID.String()), policy.ActionRead,
|
||||
)
|
||||
}))
|
||||
s.Run("OrgPermissions/UpdateCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
orgID := uuid.New()
|
||||
customRole := dbgen.CustomRole(s.T(), db, database.CustomRole{
|
||||
OrganizationID: uuid.NullUUID{
|
||||
UUID: orgID,
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
|
||||
check.Args(database.UpdateCustomRoleParams{
|
||||
Name: customRole.Name,
|
||||
DisplayName: "Test Name",
|
||||
OrganizationID: customRole.OrganizationID,
|
||||
SitePermissions: nil,
|
||||
OrgPermissions: db2sdk.List(codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
codersdk.ResourceTemplate: {codersdk.ActionCreate, codersdk.ActionRead},
|
||||
}), convertSDKPerm),
|
||||
UserPermissions: nil,
|
||||
}).Asserts(
|
||||
// First check
|
||||
rbac.ResourceAssignOrgRole.InOrg(orgID), policy.ActionUpdate,
|
||||
// Escalation checks
|
||||
rbac.ResourceTemplate.InOrg(orgID), policy.ActionCreate,
|
||||
rbac.ResourceTemplate.InOrg(orgID), policy.ActionRead,
|
||||
)
|
||||
}))
|
||||
s.Run("Blank/InsertCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
// Blank is no perms in the role
|
||||
check.Args(database.InsertCustomRoleParams{
|
||||
Name: "test",
|
||||
DisplayName: "Test Name",
|
||||
SitePermissions: nil,
|
||||
@ -1292,8 +1360,8 @@ func (s *MethodTestSuite) TestUser() {
|
||||
UserPermissions: nil,
|
||||
}).Asserts(rbac.ResourceAssignRole, policy.ActionCreate)
|
||||
}))
|
||||
s.Run("SitePermissions/UpsertCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.UpsertCustomRoleParams{
|
||||
s.Run("SitePermissions/InsertCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.InsertCustomRoleParams{
|
||||
Name: "test",
|
||||
DisplayName: "Test Name",
|
||||
SitePermissions: db2sdk.List(codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
@ -1316,9 +1384,9 @@ func (s *MethodTestSuite) TestUser() {
|
||||
rbac.ResourceWorkspace.WithOwner(testActorID.String()), policy.ActionRead,
|
||||
)
|
||||
}))
|
||||
s.Run("OrgPermissions/UpsertCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
s.Run("OrgPermissions/InsertCustomRole", s.Subtest(func(db database.Store, check *expects) {
|
||||
orgID := uuid.New()
|
||||
check.Args(database.UpsertCustomRoleParams{
|
||||
check.Args(database.InsertCustomRoleParams{
|
||||
Name: "test",
|
||||
DisplayName: "Test Name",
|
||||
OrganizationID: uuid.NullUUID{
|
||||
@ -1329,17 +1397,13 @@ func (s *MethodTestSuite) TestUser() {
|
||||
OrgPermissions: db2sdk.List(codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
codersdk.ResourceTemplate: {codersdk.ActionCreate, codersdk.ActionRead},
|
||||
}), convertSDKPerm),
|
||||
UserPermissions: db2sdk.List(codersdk.CreatePermissions(map[codersdk.RBACResource][]codersdk.RBACAction{
|
||||
codersdk.ResourceWorkspace: {codersdk.ActionRead},
|
||||
}), convertSDKPerm),
|
||||
UserPermissions: nil,
|
||||
}).Asserts(
|
||||
// First check
|
||||
rbac.ResourceAssignOrgRole.InOrg(orgID), policy.ActionCreate,
|
||||
// Escalation checks
|
||||
rbac.ResourceTemplate.InOrg(orgID), policy.ActionCreate,
|
||||
rbac.ResourceTemplate.InOrg(orgID), policy.ActionRead,
|
||||
|
||||
rbac.ResourceWorkspace.WithOwner(testActorID.String()), policy.ActionRead,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
@ -880,7 +880,7 @@ func OAuth2ProviderAppToken(t testing.TB, db database.Store, seed database.OAuth
|
||||
}
|
||||
|
||||
func CustomRole(t testing.TB, db database.Store, seed database.CustomRole) database.CustomRole {
|
||||
role, err := db.UpsertCustomRole(genCtx, database.UpsertCustomRoleParams{
|
||||
role, err := db.InsertCustomRole(genCtx, database.InsertCustomRoleParams{
|
||||
Name: takeFirst(seed.Name, strings.ToLower(testutil.GetRandomName(t))),
|
||||
DisplayName: testutil.GetRandomName(t),
|
||||
OrganizationID: seed.OrganizationID,
|
||||
|
@ -6161,6 +6161,37 @@ func (q *FakeQuerier) InsertAuditLog(_ context.Context, arg database.InsertAudit
|
||||
return alog, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertCustomRole(_ context.Context, arg database.InsertCustomRoleParams) (database.CustomRole, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
for i := range q.customRoles {
|
||||
if strings.EqualFold(q.customRoles[i].Name, arg.Name) &&
|
||||
q.customRoles[i].OrganizationID.UUID == arg.OrganizationID.UUID {
|
||||
return database.CustomRole{}, errUniqueConstraint
|
||||
}
|
||||
}
|
||||
|
||||
role := database.CustomRole{
|
||||
ID: uuid.New(),
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
OrganizationID: arg.OrganizationID,
|
||||
SitePermissions: arg.SitePermissions,
|
||||
OrgPermissions: arg.OrgPermissions,
|
||||
UserPermissions: arg.UserPermissions,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}
|
||||
q.customRoles = append(q.customRoles, role)
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertDBCryptKey(_ context.Context, arg database.InsertDBCryptKeyParams) error {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
@ -7531,6 +7562,29 @@ func (q *FakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPI
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateCustomRole(_ context.Context, arg database.UpdateCustomRoleParams) (database.CustomRole, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
for i := range q.customRoles {
|
||||
if strings.EqualFold(q.customRoles[i].Name, arg.Name) &&
|
||||
q.customRoles[i].OrganizationID.UUID == arg.OrganizationID.UUID {
|
||||
q.customRoles[i].DisplayName = arg.DisplayName
|
||||
q.customRoles[i].OrganizationID = arg.OrganizationID
|
||||
q.customRoles[i].SitePermissions = arg.SitePermissions
|
||||
q.customRoles[i].OrgPermissions = arg.OrgPermissions
|
||||
q.customRoles[i].UserPermissions = arg.UserPermissions
|
||||
q.customRoles[i].UpdatedAt = dbtime.Now()
|
||||
return q.customRoles[i], nil
|
||||
}
|
||||
}
|
||||
return database.CustomRole{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateExternalAuthLink(_ context.Context, arg database.UpdateExternalAuthLinkParams) (database.ExternalAuthLink, error) {
|
||||
if err := validateDatabaseType(arg); err != nil {
|
||||
return database.ExternalAuthLink{}, err
|
||||
@ -8875,42 +8929,6 @@ func (q *FakeQuerier) UpsertApplicationName(_ context.Context, data string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpsertCustomRole(_ context.Context, arg database.UpsertCustomRoleParams) (database.CustomRole, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return database.CustomRole{}, err
|
||||
}
|
||||
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
for i := range q.customRoles {
|
||||
if strings.EqualFold(q.customRoles[i].Name, arg.Name) {
|
||||
q.customRoles[i].DisplayName = arg.DisplayName
|
||||
q.customRoles[i].OrganizationID = arg.OrganizationID
|
||||
q.customRoles[i].SitePermissions = arg.SitePermissions
|
||||
q.customRoles[i].OrgPermissions = arg.OrgPermissions
|
||||
q.customRoles[i].UserPermissions = arg.UserPermissions
|
||||
q.customRoles[i].UpdatedAt = dbtime.Now()
|
||||
return q.customRoles[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
role := database.CustomRole{
|
||||
ID: uuid.New(),
|
||||
Name: arg.Name,
|
||||
DisplayName: arg.DisplayName,
|
||||
OrganizationID: arg.OrganizationID,
|
||||
SitePermissions: arg.SitePermissions,
|
||||
OrgPermissions: arg.OrgPermissions,
|
||||
UserPermissions: arg.UserPermissions,
|
||||
CreatedAt: dbtime.Now(),
|
||||
UpdatedAt: dbtime.Now(),
|
||||
}
|
||||
q.customRoles = append(q.customRoles, role)
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpsertDefaultProxy(_ context.Context, arg database.UpsertDefaultProxyParams) error {
|
||||
q.defaultProxyDisplayName = arg.DisplayName
|
||||
q.defaultProxyIconURL = arg.IconUrl
|
||||
|
@ -1586,6 +1586,13 @@ func (m metricsStore) InsertAuditLog(ctx context.Context, arg database.InsertAud
|
||||
return log, err
|
||||
}
|
||||
|
||||
func (m metricsStore) InsertCustomRole(ctx context.Context, arg database.InsertCustomRoleParams) (database.CustomRole, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertCustomRole(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertCustomRole").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) InsertDBCryptKey(ctx context.Context, arg database.InsertDBCryptKeyParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.InsertDBCryptKey(ctx, arg)
|
||||
@ -1957,6 +1964,13 @@ func (m metricsStore) UpdateAPIKeyByID(ctx context.Context, arg database.UpdateA
|
||||
return err
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateCustomRole(ctx context.Context, arg database.UpdateCustomRoleParams) (database.CustomRole, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateCustomRole(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateCustomRole").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateExternalAuthLink(ctx context.Context, arg database.UpdateExternalAuthLinkParams) (database.ExternalAuthLink, error) {
|
||||
start := time.Now()
|
||||
link, err := m.s.UpdateExternalAuthLink(ctx, arg)
|
||||
@ -2370,13 +2384,6 @@ func (m metricsStore) UpsertApplicationName(ctx context.Context, value string) e
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertCustomRole(ctx context.Context, arg database.UpsertCustomRoleParams) (database.CustomRole, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpsertCustomRole(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpsertCustomRole").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertDefaultProxy(ctx context.Context, arg database.UpsertDefaultProxyParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpsertDefaultProxy(ctx, arg)
|
||||
|
@ -3338,6 +3338,21 @@ func (mr *MockStoreMockRecorder) InsertAuditLog(arg0, arg1 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAuditLog", reflect.TypeOf((*MockStore)(nil).InsertAuditLog), arg0, arg1)
|
||||
}
|
||||
|
||||
// InsertCustomRole mocks base method.
|
||||
func (m *MockStore) InsertCustomRole(arg0 context.Context, arg1 database.InsertCustomRoleParams) (database.CustomRole, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertCustomRole", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.CustomRole)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertCustomRole indicates an expected call of InsertCustomRole.
|
||||
func (mr *MockStoreMockRecorder) InsertCustomRole(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertCustomRole", reflect.TypeOf((*MockStore)(nil).InsertCustomRole), arg0, arg1)
|
||||
}
|
||||
|
||||
// InsertDBCryptKey mocks base method.
|
||||
func (m *MockStore) InsertDBCryptKey(arg0 context.Context, arg1 database.InsertDBCryptKeyParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
@ -4130,6 +4145,21 @@ func (mr *MockStoreMockRecorder) UpdateAPIKeyByID(arg0, arg1 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAPIKeyByID", reflect.TypeOf((*MockStore)(nil).UpdateAPIKeyByID), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateCustomRole mocks base method.
|
||||
func (m *MockStore) UpdateCustomRole(arg0 context.Context, arg1 database.UpdateCustomRoleParams) (database.CustomRole, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateCustomRole", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.CustomRole)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateCustomRole indicates an expected call of UpdateCustomRole.
|
||||
func (mr *MockStoreMockRecorder) UpdateCustomRole(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateCustomRole", reflect.TypeOf((*MockStore)(nil).UpdateCustomRole), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateExternalAuthLink mocks base method.
|
||||
func (m *MockStore) UpdateExternalAuthLink(arg0 context.Context, arg1 database.UpdateExternalAuthLinkParams) (database.ExternalAuthLink, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -4980,21 +5010,6 @@ func (mr *MockStoreMockRecorder) UpsertApplicationName(arg0, arg1 any) *gomock.C
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertApplicationName", reflect.TypeOf((*MockStore)(nil).UpsertApplicationName), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertCustomRole mocks base method.
|
||||
func (m *MockStore) UpsertCustomRole(arg0 context.Context, arg1 database.UpsertCustomRoleParams) (database.CustomRole, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpsertCustomRole", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.CustomRole)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpsertCustomRole indicates an expected call of UpsertCustomRole.
|
||||
func (mr *MockStoreMockRecorder) UpsertCustomRole(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertCustomRole", reflect.TypeOf((*MockStore)(nil).UpsertCustomRole), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertDefaultProxy mocks base method.
|
||||
func (m *MockStore) UpsertDefaultProxy(arg0 context.Context, arg1 database.UpsertDefaultProxyParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
2
coderd/database/dump.sql
generated
2
coderd/database/dump.sql
generated
@ -1583,7 +1583,7 @@ ALTER TABLE ONLY audit_logs
|
||||
ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY custom_roles
|
||||
ADD CONSTRAINT custom_roles_pkey PRIMARY KEY (name);
|
||||
ADD CONSTRAINT custom_roles_unique_key UNIQUE (name, organization_id);
|
||||
|
||||
ALTER TABLE ONLY dbcrypt_keys
|
||||
ADD CONSTRAINT dbcrypt_keys_active_key_digest_key UNIQUE (active_key_digest);
|
||||
|
@ -0,0 +1,5 @@
|
||||
ALTER TABLE custom_roles
|
||||
DROP CONSTRAINT custom_roles_unique_key;
|
||||
|
||||
ALTER TABLE custom_roles
|
||||
ADD CONSTRAINT custom_roles_pkey PRIMARY KEY (name);
|
@ -0,0 +1,6 @@
|
||||
ALTER TABLE custom_roles
|
||||
DROP CONSTRAINT custom_roles_pkey;
|
||||
|
||||
-- Roles are unique to the organization.
|
||||
ALTER TABLE custom_roles
|
||||
ADD CONSTRAINT custom_roles_unique_key UNIQUE (name, organization_id);
|
@ -335,6 +335,7 @@ type sqlcQuerier interface {
|
||||
// every member of the org.
|
||||
InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error)
|
||||
InsertAuditLog(ctx context.Context, arg InsertAuditLogParams) (AuditLog, error)
|
||||
InsertCustomRole(ctx context.Context, arg InsertCustomRoleParams) (CustomRole, error)
|
||||
InsertDBCryptKey(ctx context.Context, arg InsertDBCryptKeyParams) error
|
||||
InsertDERPMeshKey(ctx context.Context, value string) error
|
||||
InsertDeploymentID(ctx context.Context, value string) error
|
||||
@ -402,6 +403,7 @@ type sqlcQuerier interface {
|
||||
UnarchiveTemplateVersion(ctx context.Context, arg UnarchiveTemplateVersionParams) error
|
||||
UnfavoriteWorkspace(ctx context.Context, id uuid.UUID) error
|
||||
UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error
|
||||
UpdateCustomRole(ctx context.Context, arg UpdateCustomRoleParams) (CustomRole, error)
|
||||
UpdateExternalAuthLink(ctx context.Context, arg UpdateExternalAuthLinkParams) (ExternalAuthLink, error)
|
||||
UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyParams) (GitSSHKey, error)
|
||||
UpdateGroupByID(ctx context.Context, arg UpdateGroupByIDParams) (Group, error)
|
||||
@ -462,7 +464,6 @@ type sqlcQuerier interface {
|
||||
UpsertAnnouncementBanners(ctx context.Context, value string) error
|
||||
UpsertAppSecurityKey(ctx context.Context, value string) error
|
||||
UpsertApplicationName(ctx context.Context, value string) error
|
||||
UpsertCustomRole(ctx context.Context, arg UpsertCustomRoleParams) (CustomRole, error)
|
||||
// The default proxy is implied and not actually stored in the database.
|
||||
// So we need to store it's configuration here for display purposes.
|
||||
// The functional values are immutable and controlled implicitly.
|
||||
|
@ -579,7 +579,7 @@ func TestReadCustomRoles(t *testing.T) {
|
||||
orgID = uuid.NullUUID{}
|
||||
}
|
||||
|
||||
role, err := db.UpsertCustomRole(ctx, database.UpsertCustomRoleParams{
|
||||
role, err := db.InsertCustomRole(ctx, database.InsertCustomRoleParams{
|
||||
Name: fmt.Sprintf("role-%d", i),
|
||||
OrganizationID: orgID,
|
||||
})
|
||||
|
@ -6547,40 +6547,33 @@ func (q *sqlQuerier) DeleteCustomRole(ctx context.Context, arg DeleteCustomRoleP
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertCustomRole = `-- name: UpsertCustomRole :one
|
||||
const insertCustomRole = `-- name: InsertCustomRole :one
|
||||
INSERT INTO
|
||||
custom_roles (
|
||||
name,
|
||||
display_name,
|
||||
organization_id,
|
||||
site_permissions,
|
||||
org_permissions,
|
||||
user_permissions,
|
||||
created_at,
|
||||
updated_at
|
||||
name,
|
||||
display_name,
|
||||
organization_id,
|
||||
site_permissions,
|
||||
org_permissions,
|
||||
user_permissions,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
-- Always force lowercase names
|
||||
lower($1),
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
now(),
|
||||
now()
|
||||
-- Always force lowercase names
|
||||
lower($1),
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
ON CONFLICT (name)
|
||||
DO UPDATE SET
|
||||
display_name = $2,
|
||||
site_permissions = $4,
|
||||
org_permissions = $5,
|
||||
user_permissions = $6,
|
||||
updated_at = now()
|
||||
RETURNING name, display_name, site_permissions, org_permissions, user_permissions, created_at, updated_at, organization_id, id
|
||||
`
|
||||
|
||||
type UpsertCustomRoleParams struct {
|
||||
type InsertCustomRoleParams struct {
|
||||
Name string `db:"name" json:"name"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
OrganizationID uuid.NullUUID `db:"organization_id" json:"organization_id"`
|
||||
@ -6589,8 +6582,8 @@ type UpsertCustomRoleParams struct {
|
||||
UserPermissions CustomRolePermissions `db:"user_permissions" json:"user_permissions"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpsertCustomRole(ctx context.Context, arg UpsertCustomRoleParams) (CustomRole, error) {
|
||||
row := q.db.QueryRowContext(ctx, upsertCustomRole,
|
||||
func (q *sqlQuerier) InsertCustomRole(ctx context.Context, arg InsertCustomRoleParams) (CustomRole, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertCustomRole,
|
||||
arg.Name,
|
||||
arg.DisplayName,
|
||||
arg.OrganizationID,
|
||||
@ -6613,6 +6606,54 @@ func (q *sqlQuerier) UpsertCustomRole(ctx context.Context, arg UpsertCustomRoleP
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateCustomRole = `-- name: UpdateCustomRole :one
|
||||
UPDATE
|
||||
custom_roles
|
||||
SET
|
||||
display_name = $1,
|
||||
site_permissions = $2,
|
||||
org_permissions = $3,
|
||||
user_permissions = $4,
|
||||
updated_at = now()
|
||||
WHERE
|
||||
name = lower($5)
|
||||
AND organization_id = $6
|
||||
RETURNING name, display_name, site_permissions, org_permissions, user_permissions, created_at, updated_at, organization_id, id
|
||||
`
|
||||
|
||||
type UpdateCustomRoleParams struct {
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
SitePermissions CustomRolePermissions `db:"site_permissions" json:"site_permissions"`
|
||||
OrgPermissions CustomRolePermissions `db:"org_permissions" json:"org_permissions"`
|
||||
UserPermissions CustomRolePermissions `db:"user_permissions" json:"user_permissions"`
|
||||
Name string `db:"name" json:"name"`
|
||||
OrganizationID uuid.NullUUID `db:"organization_id" json:"organization_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateCustomRole(ctx context.Context, arg UpdateCustomRoleParams) (CustomRole, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateCustomRole,
|
||||
arg.DisplayName,
|
||||
arg.SitePermissions,
|
||||
arg.OrgPermissions,
|
||||
arg.UserPermissions,
|
||||
arg.Name,
|
||||
arg.OrganizationID,
|
||||
)
|
||||
var i CustomRole
|
||||
err := row.Scan(
|
||||
&i.Name,
|
||||
&i.DisplayName,
|
||||
&i.SitePermissions,
|
||||
&i.OrgPermissions,
|
||||
&i.UserPermissions,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.OrganizationID,
|
||||
&i.ID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getAnnouncementBanners = `-- name: GetAnnouncementBanners :one
|
||||
SELECT value FROM site_configs WHERE key = 'announcement_banners'
|
||||
`
|
||||
|
@ -33,35 +33,41 @@ WHERE
|
||||
AND organization_id = @organization_id
|
||||
;
|
||||
|
||||
-- name: UpsertCustomRole :one
|
||||
-- name: InsertCustomRole :one
|
||||
INSERT INTO
|
||||
custom_roles (
|
||||
name,
|
||||
display_name,
|
||||
organization_id,
|
||||
site_permissions,
|
||||
org_permissions,
|
||||
user_permissions,
|
||||
created_at,
|
||||
updated_at
|
||||
name,
|
||||
display_name,
|
||||
organization_id,
|
||||
site_permissions,
|
||||
org_permissions,
|
||||
user_permissions,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (
|
||||
-- Always force lowercase names
|
||||
lower(@name),
|
||||
@display_name,
|
||||
@organization_id,
|
||||
@site_permissions,
|
||||
@org_permissions,
|
||||
@user_permissions,
|
||||
now(),
|
||||
now()
|
||||
-- Always force lowercase names
|
||||
lower(@name),
|
||||
@display_name,
|
||||
@organization_id,
|
||||
@site_permissions,
|
||||
@org_permissions,
|
||||
@user_permissions,
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
ON CONFLICT (name)
|
||||
DO UPDATE SET
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateCustomRole :one
|
||||
UPDATE
|
||||
custom_roles
|
||||
SET
|
||||
display_name = @display_name,
|
||||
site_permissions = @site_permissions,
|
||||
org_permissions = @org_permissions,
|
||||
user_permissions = @user_permissions,
|
||||
updated_at = now()
|
||||
RETURNING *
|
||||
;
|
||||
WHERE
|
||||
name = lower(@name)
|
||||
AND organization_id = @organization_id
|
||||
RETURNING *;
|
||||
|
@ -9,7 +9,7 @@ const (
|
||||
UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id);
|
||||
UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
|
||||
UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
|
||||
UniqueCustomRolesPkey UniqueConstraint = "custom_roles_pkey" // ALTER TABLE ONLY custom_roles ADD CONSTRAINT custom_roles_pkey PRIMARY KEY (name);
|
||||
UniqueCustomRolesUniqueKey UniqueConstraint = "custom_roles_unique_key" // ALTER TABLE ONLY custom_roles ADD CONSTRAINT custom_roles_unique_key UNIQUE (name, organization_id);
|
||||
UniqueDbcryptKeysActiveKeyDigestKey UniqueConstraint = "dbcrypt_keys_active_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_active_key_digest_key UNIQUE (active_key_digest);
|
||||
UniqueDbcryptKeysPkey UniqueConstraint = "dbcrypt_keys_pkey" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_pkey PRIMARY KEY (number);
|
||||
UniqueDbcryptKeysRevokedKeyDigestKey UniqueConstraint = "dbcrypt_keys_revoked_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_revoked_key_digest_key UNIQUE (revoked_key_digest);
|
||||
|
Reference in New Issue
Block a user