feat: added organizationId/employee id as preferred username (#3237)

Signed-off-by: Drew Foehn <drew@pixelburn.net>
Signed-off-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
Drew Foehn
2025-11-07 19:44:32 +01:00
committed by GitHub
parent 110d51d1d7
commit 87827435ce
7 changed files with 185 additions and 14 deletions
+76 -7
View File
@@ -40,6 +40,8 @@ type GoogleProvider struct {
// Refresh. `Authorize` uses the results of this saved in `session.Groups`
// Since it is called on every request.
groupValidator func(*sessions.SessionState) bool
setPreferredUsername func(s *sessions.SessionState) error
}
var _ Provider = (*GoogleProvider)(nil)
@@ -100,17 +102,59 @@ func NewGoogleProvider(p *ProviderData, opts options.GoogleOptions) (*GoogleProv
groupValidator: func(*sessions.SessionState) bool {
return true
},
setPreferredUsername: func(_ *sessions.SessionState) error {
return nil
},
}
if opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials {
provider.configureGroups(opts)
}
if opts.UseOrganizationID || opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials {
// reuse admin service to avoid multiple calls for token
var adminService *admin.Service
if opts.UseOrganizationID {
// add user scopes to admin api
userScope := getAdminAPIUserScope(opts.AdminAPIUserScope)
for index, scope := range possibleScopesList {
possibleScopesList[index] = scope + " " + userScope
}
adminService = getAdminService(opts)
provider.setPreferredUsername = func(s *sessions.SessionState) error {
userName, err := getUserInfo(adminService, s.Email)
if err != nil {
return err
}
s.PreferredUsername = userName
return nil
}
}
if opts.ServiceAccountJSON != "" || opts.UseApplicationDefaultCredentials {
if adminService == nil {
adminService = getAdminService(opts)
}
provider.configureGroups(opts, adminService)
}
}
return provider, nil
}
func (p *GoogleProvider) configureGroups(opts options.GoogleOptions) {
adminService := getAdminService(opts)
// by default can be readonly user scope
func getAdminAPIUserScope(scope string) string {
switch scope {
case "cloud":
return admin.CloudPlatformScope
case "user":
return admin.AdminDirectoryUserScope
}
return admin.AdminDirectoryUserReadonlyScope
}
func (p *GoogleProvider) configureGroups(opts options.GoogleOptions, adminService *admin.Service) {
// Backwards compatibility with `--google-group` option
if len(opts.Groups) > 0 {
p.setAllowedGroups(opts.Groups)
@@ -204,6 +248,7 @@ func (p *GoogleProvider) Redeem(ctx context.Context, redirectURL, code, codeVeri
// EnrichSession checks the listed Google Groups configured and adds any
// that the user is a member of to session.Groups.
// if preferred username is configured to be organization ID, it sets that as well.
func (p *GoogleProvider) EnrichSession(_ context.Context, s *sessions.SessionState) error {
// TODO (@NickMeves) - Move to pure EnrichSession logic and stop
// reusing legacy `groupValidator`.
@@ -212,7 +257,7 @@ func (p *GoogleProvider) EnrichSession(_ context.Context, s *sessions.SessionSta
// populating logic.
p.groupValidator(s)
return nil
return p.setPreferredUsername(s)
}
// SetGroupRestriction configures the GoogleProvider to restrict access to the
@@ -262,7 +307,7 @@ func getOauth2TokenSource(ctx context.Context, opts options.GoogleOptions, scope
if opts.UseApplicationDefaultCredentials {
ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
TargetPrincipal: getTargetPrincipal(ctx, opts),
Scopes: []string{scope},
Scopes: strings.Split(scope, " "),
Subject: opts.AdminEmail,
})
if err != nil {
@@ -364,6 +409,30 @@ func getTargetPrincipal(ctx context.Context, opts options.GoogleOptions) (target
return targetPrincipal
}
func getUserInfo(service *admin.Service, email string) (string, error) {
req := service.Users.Get(email)
user, err := req.Do()
if err != nil {
return "", fmt.Errorf("failed to get user details for %s: %v", email, err)
}
ext, _ := user.ExternalIds.([]interface{})
for _, v := range ext {
m, _ := v.(map[string]interface{})
if m == nil {
continue
}
if t, _ := m["type"].(string); t != "organization" {
continue
}
if val, _ := m["value"].(string); val != "" {
return val, nil
}
}
return "", fmt.Errorf("failed to get organization id for %s", email)
}
// getUserGroups retrieves all groups that a user is a member of using the Google Admin Directory API
func getUserGroups(service *admin.Service, email string) ([]string, error) {
var allGroups []string
+80
View File
@@ -325,3 +325,83 @@ func TestGoogleProvider_getUserGroups(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, []string{"group1@example.com", "group2@example.com"}, groups)
}
func TestGoogleProvider_getUserInfo(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/admin/directory/v1/users/test@example.com" {
response := `{
"kind": "admin#directory#user",
"id": "",
"etag": "\"\"",
"primaryEmail": "test@example.com",
"name": {
"givenName": "Test",
"familyName": "User",
"fullName": "Test User"
},
"isAdmin": false,
"isDelegatedAdmin": false,
"lastLoginTime": "",
"creationTime": "",
"agreedToTerms": true,
"suspended": false,
"archived": false,
"changePasswordAtNextLogin": false,
"ipWhitelisted": false,
"emails": [
{
"address": "test@example.com",
"primary": true
}
],
"externalIds": [
{
"value": "test.user",
"type": "organization"
}
],
"organizations": [
],
"phones": [
],
"languages": [
{
"languageCode": "en",
"preference": "preferred"
}
],
"aliases": [
"test.user@example.com"
],
"nonEditableAliases": [
"test.user@example.com"
],
"gender": {
"type": "male"
},
"customerId": "",
"orgUnitPath": "/",
"isMailboxSetup": true,
"isEnrolledIn2Sv": true,
"isEnforcedIn2Sv": false,
"includeInGlobalAddressList": true,
"thumbnailPhotoUrl": "",
"thumbnailPhotoEtag": "\"\"",
"recoveryEmail": "test.user@gmail.com",
"recoveryPhone": "+55555555555"
}`
fmt.Fprintln(w, response)
} else {
http.NotFound(w, r)
}
}))
defer ts.Close()
client := &http.Client{}
adminService, err := admin.NewService(context.Background(), option.WithHTTPClient(client), option.WithEndpoint(ts.URL))
assert.NoError(t, err)
info, err := getUserInfo(adminService, "test@example.com")
assert.NoError(t, err)
assert.Equal(t, "test.user", info)
}