oidc groups: store and expose the OIDC groups claim
Add a Groups column on the User model populated from the OIDC 'groups' claim at login, and surface it through the gRPC/REST User message so external tools (Headplane) can read group membership without reaching into the database. - proto: add 'repeated string groups = 9' to v1.User. - types.User: Groups text column, GetGroups/SetGroups JSON helpers, FromClaim populates from claims.Groups. The existing FlexibleStringSlice on OIDCClaims.Groups already handles JumpCloud-style single-string emission. - types.User.Proto(): populate v1.User.Groups via GetGroups(). - db migration 202505141323: add the column BEFORE the existing 202505141324 migration that loads users via the struct, so the schema is in place before any migration touches the User type. - db migration 202507021200: extend the inline CREATE TABLE users and INSERT INTO users ... SELECT FROM users_old to carry the new column through the SQLite schema-recreation step. - schema.sql: declare the column so squibble.Validate accepts databases produced by the new migration chain. Verified against all 7 historical sqlite dumps in hscontrol/db/testdata/sqlite. - types.UserView, types_clone.go: regenerated to expose Groups. - config-example.yaml, docs/ref/oidc.md: note the 'groups' scope and the role the column plays for external integrations. - integration/oidc_groups_test.go: verify the round-trip via headscale.ListUsers() for users with multi-group, single-group, and empty group memberships.
This commit is contained in:
parent
5228cb1a40
commit
209ba5c4ea
12 changed files with 266 additions and 7 deletions
|
|
@ -215,6 +215,27 @@ AND auth_key_id NOT IN (
|
|||
},
|
||||
Rollback: func(db *gorm.DB) error { return nil },
|
||||
},
|
||||
// Add groups column to users table for OIDC role mapping.
|
||||
// Must run before any migration that loads users via the User struct
|
||||
// (e.g., 202505141324), since the User struct now includes Groups.
|
||||
{
|
||||
ID: "202505141323",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
if !tx.Migrator().HasColumn(&types.User{}, "groups") {
|
||||
err := tx.Migrator().AddColumn(&types.User{}, "groups")
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding groups column to users table: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Rollback: func(db *gorm.DB) error {
|
||||
if db.Migrator().HasColumn(&types.User{}, "groups") {
|
||||
return db.Migrator().DropColumn(&types.User{}, "groups")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
// Fix the provider identifier for users that have a double slash in the
|
||||
// provider identifier.
|
||||
{
|
||||
|
|
@ -315,6 +336,7 @@ AND auth_key_id NOT IN (
|
|||
provider_identifier text,
|
||||
provider text,
|
||||
profile_pic_url text,
|
||||
groups text,
|
||||
created_at datetime,
|
||||
updated_at datetime,
|
||||
deleted_at datetime
|
||||
|
|
@ -381,8 +403,8 @@ AND auth_key_id NOT IN (
|
|||
|
||||
// Copy data directly using SQL
|
||||
dataCopySQL := []string{
|
||||
`INSERT INTO users (id, name, display_name, email, provider_identifier, provider, profile_pic_url, created_at, updated_at, deleted_at)
|
||||
SELECT id, name, display_name, email, provider_identifier, provider, profile_pic_url, created_at, updated_at, deleted_at
|
||||
`INSERT INTO users (id, name, display_name, email, provider_identifier, provider, profile_pic_url, groups, created_at, updated_at, deleted_at)
|
||||
SELECT id, name, display_name, email, provider_identifier, provider, profile_pic_url, groups, created_at, updated_at, deleted_at
|
||||
FROM users_old`,
|
||||
|
||||
`INSERT INTO pre_auth_keys (id, key, user_id, reusable, ephemeral, used, tags, expiration, created_at)
|
||||
|
|
@ -1000,6 +1022,7 @@ func runMigrations(cfg types.DatabaseConfig, dbConn *gorm.DB, migrations *gormig
|
|||
"202502131714",
|
||||
"202502171819",
|
||||
"202505091439",
|
||||
"202505141323",
|
||||
"202505141324",
|
||||
|
||||
// As of 2025-07-02, no new IDs should be added here.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue