feat: implement patch and get api methods for role sync (#14692)

* feat: implement patch and get api methods for role sync
This commit is contained in:
Steven Masley
2024-09-17 10:38:42 -05:00
committed by GitHub
parent be516f9686
commit ce21b2030a
11 changed files with 648 additions and 132 deletions

167
coderd/apidoc/docs.go generated
View File

@ -3155,7 +3155,7 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/idpsync.GroupSyncSettings"
"$ref": "#/definitions/codersdk.GroupSyncSettings"
}
}
}
@ -3188,7 +3188,75 @@ const docTemplate = `{
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/idpsync.GroupSyncSettings"
"$ref": "#/definitions/codersdk.GroupSyncSettings"
}
}
}
}
},
"/organizations/{organization}/settings/idpsync/roles": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Enterprise"
],
"summary": "Get role IdP Sync settings by organization",
"operationId": "get-role-idp-sync-settings-by-organization",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RoleSyncSettings"
}
}
}
},
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": [
"application/json"
],
"tags": [
"Enterprise"
],
"summary": "Update role IdP Sync settings by organization",
"operationId": "update-role-idp-sync-settings-by-organization",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RoleSyncSettings"
}
}
}
@ -10523,6 +10591,44 @@ const docTemplate = `{
"GroupSourceOIDC"
]
},
"codersdk.GroupSyncSettings": {
"type": "object",
"properties": {
"auto_create_missing_groups": {
"description": "AutoCreateMissing controls whether groups returned by the OIDC provider\nare automatically created in Coder if they are missing.",
"type": "boolean"
},
"field": {
"description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.",
"type": "string"
},
"legacy_group_name_mapping": {
"description": "LegacyNameMapping is deprecated. It remaps an IDP group name to\na Coder group name. Since configuration is now done at runtime,\ngroup IDs are used to account for group renames.\nFor legacy configurations, this config option has to remain.\nDeprecated: Use Mapping instead.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"mapping": {
"description": "Mapping maps from an OIDC group --\u003e Coder group ID",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"regex_filter": {
"description": "RegexFilter is a regular expression that filters the groups returned by\nthe OIDC provider. Any group not matched by this regex will be ignored.\nIf the group filter is nil, then no group filtering will occur.",
"allOf": [
{
"$ref": "#/definitions/regexp.Regexp"
}
]
}
}
},
"codersdk.Healthcheck": {
"type": "object",
"properties": {
@ -12238,6 +12344,25 @@ const docTemplate = `{
}
}
},
"codersdk.RoleSyncSettings": {
"type": "object",
"properties": {
"field": {
"description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.",
"type": "string"
},
"mapping": {
"description": "Mapping maps from an OIDC group --\u003e Coder organization role",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"codersdk.SSHConfig": {
"type": "object",
"properties": {
@ -15253,44 +15378,6 @@ const docTemplate = `{
}
}
},
"idpsync.GroupSyncSettings": {
"type": "object",
"properties": {
"auto_create_missing_groups": {
"description": "AutoCreateMissing controls whether groups returned by the OIDC provider\nare automatically created in Coder if they are missing.",
"type": "boolean"
},
"field": {
"description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.",
"type": "string"
},
"legacy_group_name_mapping": {
"description": "LegacyNameMapping is deprecated. It remaps an IDP group name to\na Coder group name. Since configuration is now done at runtime,\ngroup IDs are used to account for group renames.\nFor legacy configurations, this config option has to remain.\nDeprecated: Use Mapping instead.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"mapping": {
"description": "Mapping maps from an OIDC group --\u003e Coder group ID",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"regex_filter": {
"description": "RegexFilter is a regular expression that filters the groups returned by\nthe OIDC provider. Any group not matched by this regex will be ignored.\nIf the group filter is nil, then no group filtering will occur.",
"allOf": [
{
"$ref": "#/definitions/regexp.Regexp"
}
]
}
}
},
"key.NodePublic": {
"type": "object"
},

View File

@ -2773,7 +2773,7 @@
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/idpsync.GroupSyncSettings"
"$ref": "#/definitions/codersdk.GroupSyncSettings"
}
}
}
@ -2802,7 +2802,67 @@
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/idpsync.GroupSyncSettings"
"$ref": "#/definitions/codersdk.GroupSyncSettings"
}
}
}
}
},
"/organizations/{organization}/settings/idpsync/roles": {
"get": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Enterprise"],
"summary": "Get role IdP Sync settings by organization",
"operationId": "get-role-idp-sync-settings-by-organization",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RoleSyncSettings"
}
}
}
},
"patch": {
"security": [
{
"CoderSessionToken": []
}
],
"produces": ["application/json"],
"tags": ["Enterprise"],
"summary": "Update role IdP Sync settings by organization",
"operationId": "update-role-idp-sync-settings-by-organization",
"parameters": [
{
"type": "string",
"format": "uuid",
"description": "Organization ID",
"name": "organization",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/codersdk.RoleSyncSettings"
}
}
}
@ -9445,6 +9505,44 @@
"enum": ["user", "oidc"],
"x-enum-varnames": ["GroupSourceUser", "GroupSourceOIDC"]
},
"codersdk.GroupSyncSettings": {
"type": "object",
"properties": {
"auto_create_missing_groups": {
"description": "AutoCreateMissing controls whether groups returned by the OIDC provider\nare automatically created in Coder if they are missing.",
"type": "boolean"
},
"field": {
"description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.",
"type": "string"
},
"legacy_group_name_mapping": {
"description": "LegacyNameMapping is deprecated. It remaps an IDP group name to\na Coder group name. Since configuration is now done at runtime,\ngroup IDs are used to account for group renames.\nFor legacy configurations, this config option has to remain.\nDeprecated: Use Mapping instead.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"mapping": {
"description": "Mapping maps from an OIDC group --\u003e Coder group ID",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"regex_filter": {
"description": "RegexFilter is a regular expression that filters the groups returned by\nthe OIDC provider. Any group not matched by this regex will be ignored.\nIf the group filter is nil, then no group filtering will occur.",
"allOf": [
{
"$ref": "#/definitions/regexp.Regexp"
}
]
}
}
},
"codersdk.Healthcheck": {
"type": "object",
"properties": {
@ -11070,6 +11168,25 @@
}
}
},
"codersdk.RoleSyncSettings": {
"type": "object",
"properties": {
"field": {
"description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.",
"type": "string"
},
"mapping": {
"description": "Mapping maps from an OIDC group --\u003e Coder organization role",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"codersdk.SSHConfig": {
"type": "object",
"properties": {
@ -13906,44 +14023,6 @@
}
}
},
"idpsync.GroupSyncSettings": {
"type": "object",
"properties": {
"auto_create_missing_groups": {
"description": "AutoCreateMissing controls whether groups returned by the OIDC provider\nare automatically created in Coder if they are missing.",
"type": "boolean"
},
"field": {
"description": "Field selects the claim field to be used as the created user's\ngroups. If the group field is the empty string, then no group updates\nwill ever come from the OIDC provider.",
"type": "string"
},
"legacy_group_name_mapping": {
"description": "LegacyNameMapping is deprecated. It remaps an IDP group name to\na Coder group name. Since configuration is now done at runtime,\ngroup IDs are used to account for group renames.\nFor legacy configurations, this config option has to remain.\nDeprecated: Use Mapping instead.",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"mapping": {
"description": "Mapping maps from an OIDC group --\u003e Coder group ID",
"type": "object",
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"regex_filter": {
"description": "RegexFilter is a regular expression that filters the groups returned by\nthe OIDC provider. Any group not matched by this regex will be ignored.\nIf the group filter is nil, then no group filtering will occur.",
"allOf": [
{
"$ref": "#/definitions/regexp.Regexp"
}
]
}
}
},
"key.NodePublic": {
"type": "object"
},

View File

@ -55,7 +55,8 @@ type IDPSync interface {
SiteRoleSyncEnabled() bool
// RoleSyncSettings is similar to GroupSyncSettings. See GroupSyncSettings for
// rational.
RoleSyncSettings() runtimeconfig.RuntimeEntry[*RoleSyncSettings]
RoleSyncSettings(ctx context.Context, orgID uuid.UUID, db database.Store) (*RoleSyncSettings, error)
UpdateRoleSettings(ctx context.Context, orgID uuid.UUID, db database.Store, settings RoleSyncSettings) error
// ParseRoleClaims takes claims from an OIDC provider, and returns the params
// for role syncing. Most of the logic happens in SyncRoles.
ParseRoleClaims(ctx context.Context, mergedClaims jwt.MapClaims) (RoleParams, *HTTPError)

View File

@ -16,6 +16,7 @@ import (
"github.com/coder/coder/v2/coderd/rbac/rolestore"
"github.com/coder/coder/v2/coderd/runtimeconfig"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
)
type RoleParams struct {
@ -41,8 +42,26 @@ func (AGPLIDPSync) SiteRoleSyncEnabled() bool {
return false
}
func (s AGPLIDPSync) RoleSyncSettings() runtimeconfig.RuntimeEntry[*RoleSyncSettings] {
return s.Role
func (s AGPLIDPSync) UpdateRoleSettings(ctx context.Context, orgID uuid.UUID, db database.Store, settings RoleSyncSettings) error {
orgResolver := s.Manager.OrganizationResolver(db, orgID)
err := s.SyncSettings.Role.SetRuntimeValue(ctx, orgResolver, &settings)
if err != nil {
return xerrors.Errorf("update role sync settings: %w", err)
}
return nil
}
func (s AGPLIDPSync) RoleSyncSettings(ctx context.Context, orgID uuid.UUID, db database.Store) (*RoleSyncSettings, error) {
rlv := s.Manager.OrganizationResolver(db, orgID)
settings, err := s.Role.Resolve(ctx, rlv)
if err != nil {
if !xerrors.Is(err, runtimeconfig.ErrEntryNotFound) {
return nil, xerrors.Errorf("resolve role sync settings: %w", err)
}
return &RoleSyncSettings{}, nil
}
return settings, nil
}
func (s AGPLIDPSync) ParseRoleClaims(_ context.Context, _ jwt.MapClaims) (RoleParams, *HTTPError) {
@ -85,15 +104,12 @@ func (s AGPLIDPSync) SyncRoles(ctx context.Context, db database.Store, user data
allExpected := make([]rbac.RoleIdentifier, 0)
for _, member := range orgMemberships {
orgID := member.OrganizationMember.OrganizationID
orgResolver := s.Manager.OrganizationResolver(tx, orgID)
settings, err := s.RoleSyncSettings().Resolve(ctx, orgResolver)
settings, err := s.RoleSyncSettings(ctx, orgID, tx)
if err != nil {
if !xerrors.Is(err, runtimeconfig.ErrEntryNotFound) {
return xerrors.Errorf("resolve group sync settings: %w", err)
}
// No entry means no role syncing for this organization
continue
}
if settings.Field == "" {
// Explicitly disabled role sync for this organization
continue
@ -261,14 +277,7 @@ func (AGPLIDPSync) RolesFromClaim(field string, claims jwt.MapClaims) ([]string,
return parsedRoles, nil
}
type RoleSyncSettings struct {
// Field selects the claim field to be used as the created user's
// groups. If the group field is the empty string, then no group updates
// will ever come from the OIDC provider.
Field string `json:"field"`
// Mapping maps from an OIDC group --> Coder organization role
Mapping map[string][]string `json:"mapping"`
}
type RoleSyncSettings codersdk.RoleSyncSettings
func (s *RoleSyncSettings) Set(v string) error {
return json.Unmarshal([]byte(v), s)