Skip to content

Commit 07157f9

Browse files
committed
chore: begin work to implement organization sync from oidc
1 parent 337ee35 commit 07157f9

File tree

2 files changed

+99
-23
lines changed

2 files changed

+99
-23
lines changed

coderd/userauth.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,7 @@ type OIDCConfig struct {
737737
// support the userinfo endpoint, or if the userinfo endpoint causes
738738
// undesirable behavior.
739739
IgnoreUserInfo bool
740+
740741
// GroupField selects the claim field to be used as the created user's
741742
// groups. If the group field is the empty string, then no group updates
742743
// will ever come from the OIDC provider.
@@ -756,6 +757,7 @@ type OIDCConfig struct {
756757
// to groups within Coder.
757758
// map[oidcGroupName]coderGroupName
758759
GroupMapping map[string]string
760+
759761
// UserRoleField selects the claim field to be used as the created user's
760762
// roles. If the field is the empty string, then no role updates
761763
// will ever come from the OIDC provider.
@@ -767,6 +769,17 @@ type OIDCConfig struct {
767769
// UserRolesDefault is the default set of roles to assign to a user if role sync
768770
// is enabled.
769771
UserRolesDefault []string
772+
773+
// OrganizationField selects the claim field to be used as the created user's
774+
// organizations. If the field is the empty string, then no organization updates
775+
// will ever come from the OIDC provider.
776+
OrganizationField string
777+
// OrganizationMapping controls how organizations returned by the OIDC provider get mapped
778+
OrganizationMapping map[string][]string
779+
// OrganizationAssignDefault will auto-add the user to the default organization
780+
// always. This primarily exists for single org deployments
781+
OrganizationAssignDefault bool
782+
770783
// SignInText is the text to display on the OIDC login button
771784
SignInText string
772785
// IconURL points to the URL of an icon to display on the OIDC login button
@@ -1152,6 +1165,26 @@ func (api *API) oidcGroups(ctx context.Context, mergedClaims map[string]interfac
11521165
return usingGroups, groups, nil
11531166
}
11541167

1168+
func (api *API) oidcOrganizations(ctx context.Context, mergedClaims map[string]interface{}) ([]string, *httpError) {
1169+
defaultOrg, err := api.Database.GetDefaultOrganization(ctx)
1170+
if err != nil {
1171+
return nil, &httpError{
1172+
code: http.StatusInternalServerError,
1173+
msg: "Failed to get default organization",
1174+
detail: err.Error(),
1175+
renderStaticPage: false,
1176+
}
1177+
}
1178+
1179+
userOrganizations := make([]string, 0)
1180+
1181+
if api.OIDCConfig.OrganizationAssignDefault {
1182+
userOrganizations = append(userOrganizations, defaultOrg.ID.String())
1183+
}
1184+
1185+
return userOrganizations, nil
1186+
}
1187+
11551188
// oidcRoles returns the roles for the user from the OIDC claims.
11561189
// If the function returns false, then the caller should return early.
11571190
// All writes to the response writer are handled by this function.
@@ -1264,6 +1297,10 @@ type oauthLoginParams struct {
12641297
Username string
12651298
Name string
12661299
AvatarURL string
1300+
// UsingOrganizations indicates to use organization sync
1301+
UsingOrganizations bool
1302+
Organizations []string
1303+
AssignDefaultOrganization bool
12671304
// Is UsingGroups is true, then the user will be assigned
12681305
// to the Groups provided.
12691306
UsingGroups bool

codersdk/deployment.go

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -506,29 +506,36 @@ type OIDCConfig struct {
506506
ClientID serpent.String `json:"client_id" typescript:",notnull"`
507507
ClientSecret serpent.String `json:"client_secret" typescript:",notnull"`
508508
// ClientKeyFile & ClientCertFile are used in place of ClientSecret for PKI auth.
509-
ClientKeyFile serpent.String `json:"client_key_file" typescript:",notnull"`
510-
ClientCertFile serpent.String `json:"client_cert_file" typescript:",notnull"`
511-
EmailDomain serpent.StringArray `json:"email_domain" typescript:",notnull"`
512-
IssuerURL serpent.String `json:"issuer_url" typescript:",notnull"`
513-
Scopes serpent.StringArray `json:"scopes" typescript:",notnull"`
514-
IgnoreEmailVerified serpent.Bool `json:"ignore_email_verified" typescript:",notnull"`
515-
UsernameField serpent.String `json:"username_field" typescript:",notnull"`
516-
NameField serpent.String `json:"name_field" typescript:",notnull"`
517-
EmailField serpent.String `json:"email_field" typescript:",notnull"`
518-
AuthURLParams serpent.Struct[map[string]string] `json:"auth_url_params" typescript:",notnull"`
519-
IgnoreUserInfo serpent.Bool `json:"ignore_user_info" typescript:",notnull"`
520-
GroupAutoCreate serpent.Bool `json:"group_auto_create" typescript:",notnull"`
521-
GroupRegexFilter serpent.Regexp `json:"group_regex_filter" typescript:",notnull"`
522-
GroupAllowList serpent.StringArray `json:"group_allow_list" typescript:",notnull"`
523-
GroupField serpent.String `json:"groups_field" typescript:",notnull"`
524-
GroupMapping serpent.Struct[map[string]string] `json:"group_mapping" typescript:",notnull"`
525-
UserRoleField serpent.String `json:"user_role_field" typescript:",notnull"`
526-
UserRoleMapping serpent.Struct[map[string][]string] `json:"user_role_mapping" typescript:",notnull"`
527-
UserRolesDefault serpent.StringArray `json:"user_roles_default" typescript:",notnull"`
528-
SignInText serpent.String `json:"sign_in_text" typescript:",notnull"`
529-
IconURL serpent.URL `json:"icon_url" typescript:",notnull"`
530-
SignupsDisabledText serpent.String `json:"signups_disabled_text" typescript:",notnull"`
531-
SkipIssuerChecks serpent.Bool `json:"skip_issuer_checks" typescript:",notnull"`
509+
ClientKeyFile serpent.String `json:"client_key_file" typescript:",notnull"`
510+
ClientCertFile serpent.String `json:"client_cert_file" typescript:",notnull"`
511+
EmailDomain serpent.StringArray `json:"email_domain" typescript:",notnull"`
512+
IssuerURL serpent.String `json:"issuer_url" typescript:",notnull"`
513+
Scopes serpent.StringArray `json:"scopes" typescript:",notnull"`
514+
IgnoreEmailVerified serpent.Bool `json:"ignore_email_verified" typescript:",notnull"`
515+
UsernameField serpent.String `json:"username_field" typescript:",notnull"`
516+
NameField serpent.String `json:"name_field" typescript:",notnull"`
517+
EmailField serpent.String `json:"email_field" typescript:",notnull"`
518+
AuthURLParams serpent.Struct[map[string]string] `json:"auth_url_params" typescript:",notnull"`
519+
IgnoreUserInfo serpent.Bool `json:"ignore_user_info" typescript:",notnull"`
520+
// Group Sync
521+
GroupAutoCreate serpent.Bool `json:"group_auto_create" typescript:",notnull"`
522+
GroupRegexFilter serpent.Regexp `json:"group_regex_filter" typescript:",notnull"`
523+
GroupAllowList serpent.StringArray `json:"group_allow_list" typescript:",notnull"`
524+
GroupField serpent.String `json:"groups_field" typescript:",notnull"`
525+
GroupMapping serpent.Struct[map[string]string] `json:"group_mapping" typescript:",notnull"`
526+
// Role Sync
527+
UserRoleField serpent.String `json:"user_role_field" typescript:",notnull"`
528+
UserRoleMapping serpent.Struct[map[string][]string] `json:"user_role_mapping" typescript:",notnull"`
529+
UserRolesDefault serpent.StringArray `json:"user_roles_default" typescript:",notnull"`
530+
// Organization Sync
531+
OrganizationField serpent.String `json:"organization_field" typescript:",notnull"`
532+
OrganizationMapping serpent.Struct[map[string][]string] `json:"organization_mapping" typescript:",notnull"`
533+
OrganizationAssignDefault serpent.Bool `json:"assign_to_default_organization" typescript:",notnull"`
534+
535+
SignInText serpent.String `json:"sign_in_text" typescript:",notnull"`
536+
IconURL serpent.URL `json:"icon_url" typescript:",notnull"`
537+
SignupsDisabledText serpent.String `json:"signups_disabled_text" typescript:",notnull"`
538+
SkipIssuerChecks serpent.Bool `json:"skip_issuer_checks" typescript:",notnull"`
532539
}
533540

534541
type TelemetryConfig struct {
@@ -1622,6 +1629,38 @@ when required by your organization's security policy.`,
16221629
Group: &deploymentGroupOIDC,
16231630
YAML: "userRoleDefault",
16241631
},
1632+
{
1633+
Name: "OIDC Organization Member Mapping",
1634+
Description: "A map of the OIDC passed in user claims and the organizations in Coder it should map to. Users with the claims will be assigned organization membership in Coder.",
1635+
Flag: "oidc-organization-mapping",
1636+
Env: "CODER_OIDC_ORGANIZATION_MAPPING",
1637+
Default: "{}",
1638+
Value: &c.OIDC.OrganizationMapping,
1639+
Group: &deploymentGroupOIDC,
1640+
YAML: "organizationMapping",
1641+
},
1642+
{
1643+
Name: "OIDC Assign Default Organization",
1644+
Description: "By default, coder places all OIDC users into the default organization. Set 'false' to stop this behavior.",
1645+
Flag: "oidc-organization-assign-default",
1646+
Env: "CODER_OIDC_ORGANIZATION_ASSIGN_DEFAULT",
1647+
Default: "true",
1648+
Value: &c.OIDC.OrganizationAssignDefault,
1649+
Group: &deploymentGroupOIDC,
1650+
YAML: "organizationAssignDefault",
1651+
},
1652+
{
1653+
Name: "OIDC Organization Field",
1654+
Description: "This field must be set if using the organization sync feature. Set this to the name of the claim used to store the user's organizations. The organizations should be sent as an array of strings.",
1655+
Flag: "oidc-organization-field",
1656+
Env: "CODER_OIDC_ORGANIZATION_FIELD",
1657+
// This value is intentionally blank. If this is empty, then OIDC
1658+
// organization sync behavior is disabled.
1659+
Default: "",
1660+
Value: &c.OIDC.OrganizationField,
1661+
Group: &deploymentGroupOIDC,
1662+
YAML: "organizationField",
1663+
},
16251664
{
16261665
Name: "OpenID Connect sign in text",
16271666
Description: "The text to show on the OpenID Connect sign in button.",

0 commit comments

Comments
 (0)